1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-02 12:47:41 +02:00

Chore: Convert build-translations to TS

This commit is contained in:
Laurent Cozic 2023-10-08 19:20:07 +01:00
parent 2f7801a267
commit cbe260eed2
4 changed files with 43 additions and 34 deletions

View File

@ -955,6 +955,7 @@ packages/renderer/noteStyle.js
packages/renderer/pathUtils.js packages/renderer/pathUtils.js
packages/renderer/utils.js packages/renderer/utils.js
packages/tools/build-release-stats.js packages/tools/build-release-stats.js
packages/tools/build-translation.js
packages/tools/build-welcome.js packages/tools/build-welcome.js
packages/tools/buildServerDocker.test.js packages/tools/buildServerDocker.test.js
packages/tools/buildServerDocker.js packages/tools/buildServerDocker.js

1
.gitignore vendored
View File

@ -941,6 +941,7 @@ packages/renderer/noteStyle.js
packages/renderer/pathUtils.js packages/renderer/pathUtils.js
packages/renderer/utils.js packages/renderer/utils.js
packages/tools/build-release-stats.js packages/tools/build-release-stats.js
packages/tools/build-translation.js
packages/tools/build-welcome.js packages/tools/build-welcome.js
packages/tools/buildServerDocker.test.js packages/tools/buildServerDocker.test.js
packages/tools/buildServerDocker.js packages/tools/buildServerDocker.js

View File

@ -1,5 +1,3 @@
'use strict';
// Dependencies: // Dependencies:
// //
// sudo apt install gettext sudo apt install translate-toolkit // sudo apt install gettext sudo apt install translate-toolkit
@ -7,34 +5,35 @@
// gettext v21+ is required as versions before that have bugs when parsing // gettext v21+ is required as versions before that have bugs when parsing
// JavaScript template strings which means we would lose translations. // JavaScript template strings which means we would lose translations.
const rootDir = `${__dirname}/../..`; import markdownUtils from '@joplin/lib/markdownUtils';
import { translationExecutablePath, removePoHeaderDate, mergePotToPo, parsePoFile, parseTranslations, TranslationStatus } from './utils/translation';
const markdownUtils = require('@joplin/lib/markdownUtils').default; import { execCommand, isMac, insertContentIntoFile, filename, dirname, fileExtension } from './tool-utils.js';
const fs = require('fs-extra'); import { countryDisplayName, countryCodeOnly } from '@joplin/lib/locale';
const { translationExecutablePath, removePoHeaderDate, mergePotToPo, parsePoFile, parseTranslations } = require('./utils/translation'); import { readdirSync, writeFileSync } from 'fs';
const localesDir = `${__dirname}/locales`; import { readFile } from 'fs/promises';
const libDir = `${rootDir}/packages/lib`; import { copy, mkdirpSync, remove } from 'fs-extra';
const { execCommand, isMac, insertContentIntoFile, filename, dirname, fileExtension } = require('./tool-utils.js');
const { countryDisplayName, countryCodeOnly } = require('@joplin/lib/locale');
const { GettextExtractor, JsExtractors } = require('gettext-extractor'); const { GettextExtractor, JsExtractors } = require('gettext-extractor');
function serializeTranslation(translation) { const rootDir = `${__dirname}/../..`;
const localesDir = `${__dirname}/locales`;
const libDir = `${rootDir}/packages/lib`;
function serializeTranslation(translation: string) {
const output = parseTranslations(translation); const output = parseTranslations(translation);
return JSON.stringify(output, Object.keys(output).sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : +1), ' '); return JSON.stringify(output, Object.keys(output).sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : +1), ' ');
} }
function saveToFile(filePath, data) { function saveToFile(filePath: string, data: string) {
fs.writeFileSync(filePath, data); writeFileSync(filePath, data);
} }
async function buildLocale(inputFile, outputFile) { async function buildLocale(inputFile: string, outputFile: string) {
const r = await parsePoFile(inputFile); const r = await parsePoFile(inputFile);
const translation = serializeTranslation(r); const translation = serializeTranslation(r);
saveToFile(outputFile, translation); saveToFile(outputFile, translation);
} }
async function createPotFile(potFilePath) { async function createPotFile(potFilePath: string) {
const excludedDirs = [ const excludedDirs = [
'./.git/*', './.git/*',
'./.github/*', './.github/*',
@ -82,7 +81,7 @@ async function createPotFile(potFilePath) {
// basename, such as "exmaple.js", and "example.ts", we only keep the file // basename, such as "exmaple.js", and "example.ts", we only keep the file
// with ".ts" extension (since the .js should be the compiled file). // with ".ts" extension (since the .js should be the compiled file).
const toProcess = {}; const toProcess: Record<string, string> = {};
for (const file of files) { for (const file of files) {
if (!file) continue; if (!file) continue;
@ -172,7 +171,7 @@ async function createPotFile(potFilePath) {
await removePoHeaderDate(potFilePath); await removePoHeaderDate(potFilePath);
} }
function buildIndex(locales, stats) { function buildIndex(locales: string[], stats: TranslationStatus[]) {
const output = []; const output = [];
output.push('var locales = {};'); output.push('var locales = {};');
output.push('var stats = {};'); output.push('var stats = {};');
@ -196,10 +195,10 @@ function buildIndex(locales, stats) {
return output.join('\n'); return output.join('\n');
} }
function availableLocales(defaultLocale) { function availableLocales(defaultLocale: string) {
const output = [defaultLocale]; const output = [defaultLocale];
// eslint-disable-next-line github/array-foreach -- Old code before rule was applied // eslint-disable-next-line github/array-foreach -- Old code before rule was applied
fs.readdirSync(localesDir).forEach((path) => { readdirSync(localesDir).forEach((path) => {
if (fileExtension(path) !== 'po') return; if (fileExtension(path) !== 'po') return;
const locale = filename(path); const locale = filename(path);
if (locale === defaultLocale) return; if (locale === defaultLocale) return;
@ -208,7 +207,7 @@ function availableLocales(defaultLocale) {
return output; return output;
} }
function extractTranslator(regex, poContent) { function extractTranslator(regex: RegExp, poContent: string) {
const translatorMatch = poContent.match(regex); const translatorMatch = poContent.match(regex);
let translatorName = ''; let translatorName = '';
@ -225,13 +224,13 @@ function extractTranslator(regex, poContent) {
return translatorName; return translatorName;
} }
function translatorNameToMarkdown(translatorName) { function translatorNameToMarkdown(translatorName: string) {
const matches = translatorName.match(/^(.*?)\s*\((.*)\)$/); const matches = translatorName.match(/^(.*?)\s*\((.*)\)$/);
if (!matches) return translatorName; if (!matches) return translatorName;
return `[${markdownUtils.escapeTitleText(matches[1])}](mailto:${markdownUtils.escapeLinkUrl(matches[2])})`; return `[${markdownUtils.escapeTitleText(matches[1])}](mailto:${markdownUtils.escapeLinkUrl(matches[2])})`;
} }
async function translationStatus(isDefault, poFile) { async function translationStatus(isDefault: boolean, poFile: string): Promise<TranslationStatus> {
// "apt install translate-toolkit" to have pocount // "apt install translate-toolkit" to have pocount
let pocountPath = 'pocount'; let pocountPath = 'pocount';
if (isMac()) pocountPath = translationExecutablePath('pocount'); if (isMac()) pocountPath = translationExecutablePath('pocount');
@ -248,7 +247,7 @@ async function translationStatus(isDefault, poFile) {
const untranslatedCount = Number(untranslatedMatches[1]); const untranslatedCount = Number(untranslatedMatches[1]);
let translatorName = ''; let translatorName = '';
const content = await fs.readFile(poFile, 'utf-8'); const content = await readFile(poFile, 'utf-8');
translatorName = extractTranslator(/Last-Translator:\s*?(.*)/, content); translatorName = extractTranslator(/Last-Translator:\s*?(.*)/, content);
if (!translatorName) { if (!translatorName) {
@ -276,7 +275,7 @@ async function translationStatus(isDefault, poFile) {
}; };
} }
function flagImageUrl(locale) { function flagImageUrl(locale: string) {
const baseUrl = 'https://joplinapp.org/images/flags'; const baseUrl = 'https://joplinapp.org/images/flags';
if (locale === 'ar') return `${baseUrl}/country-4x3/arableague.png`; if (locale === 'ar') return `${baseUrl}/country-4x3/arableague.png`;
if (locale === 'eu') return `${baseUrl}/es/basque_country.png`; if (locale === 'eu') return `${baseUrl}/es/basque_country.png`;
@ -292,11 +291,11 @@ function flagImageUrl(locale) {
return `${baseUrl}/country-4x3/${countryCodeOnly(locale).toLowerCase()}.png`; return `${baseUrl}/country-4x3/${countryCodeOnly(locale).toLowerCase()}.png`;
} }
function poFileUrl(locale) { function poFileUrl(locale: string) {
return `https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/${locale}.po`; return `https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/${locale}.po`;
} }
function translationStatusToMdTable(status) { function translationStatusToMdTable(status: TranslationStatus[]) {
const output = []; const output = [];
output.push(['&nbsp;', 'Language', 'Po File', 'Last translator', 'Percent done'].join(' | ')); output.push(['&nbsp;', 'Language', 'Po File', 'Last translator', 'Percent done'].join(' | '));
output.push(['---', '---', '---', '---', '---'].join('|')); output.push(['---', '---', '---', '---', '---'].join('|'));
@ -308,7 +307,7 @@ function translationStatusToMdTable(status) {
return output.join('\n'); return output.join('\n');
} }
async function updateReadmeWithStats(stats) { async function updateReadmeWithStats(stats: TranslationStatus[]) {
await insertContentIntoFile( await insertContentIntoFile(
`${rootDir}/README.md`, `${rootDir}/README.md`,
'<!-- LOCALE-TABLE-AUTO-GENERATED -->\n', '<!-- LOCALE-TABLE-AUTO-GENERATED -->\n',
@ -317,12 +316,12 @@ async function updateReadmeWithStats(stats) {
); );
} }
async function translationStrings(poFilePath) { async function translationStrings(poFilePath: string) {
const r = await parsePoFile(poFilePath); const r = await parsePoFile(poFilePath);
return Object.keys(r.translations['']); return Object.keys(r.translations['']);
} }
function deletedStrings(oldStrings, newStrings) { function deletedStrings(oldStrings: string[], newStrings: string[]) {
const output = []; const output = [];
for (const s1 of oldStrings) { for (const s1 of oldStrings) {
if (newStrings.includes(s1)) continue; if (newStrings.includes(s1)) continue;
@ -342,7 +341,7 @@ async function main() {
if (missingStringsCheckOnly) { if (missingStringsCheckOnly) {
tempPotFilePath = `${localesDir}/joplin-temp-${Math.floor(Math.random() * 10000000)}.pot`; tempPotFilePath = `${localesDir}/joplin-temp-${Math.floor(Math.random() * 10000000)}.pot`;
await fs.copy(potFilePath, tempPotFilePath); await copy(potFilePath, tempPotFilePath);
potFilePath = tempPotFilePath; potFilePath = tempPotFilePath;
} }
@ -359,7 +358,7 @@ async function main() {
console.info(`Updated pot file. Total strings: ${oldPotStatus.untranslatedCount} => ${newPotStatus.untranslatedCount}`); console.info(`Updated pot file. Total strings: ${oldPotStatus.untranslatedCount} => ${newPotStatus.untranslatedCount}`);
if (tempPotFilePath) await fs.remove(tempPotFilePath); if (tempPotFilePath) await remove(tempPotFilePath);
const deletedCount = oldPotStatus.untranslatedCount - newPotStatus.untranslatedCount; const deletedCount = oldPotStatus.untranslatedCount - newPotStatus.untranslatedCount;
if (deletedCount >= 5) { if (deletedCount >= 5) {
@ -379,7 +378,7 @@ async function main() {
await execCommand(`cp "${potFilePath}" ` + `"${localesDir}/${defaultLocale}.po"`); await execCommand(`cp "${potFilePath}" ` + `"${localesDir}/${defaultLocale}.po"`);
fs.mkdirpSync(jsonLocalesDir, 0o755); mkdirpSync(jsonLocalesDir, 0o755);
const stats = []; const stats = [];

View File

@ -2,6 +2,14 @@ import { execCommand, isMac } from '../tool-utils';
import { existsSync, readFile } from 'fs-extra'; import { existsSync, readFile } from 'fs-extra';
const gettextParser = require('gettext-parser'); const gettextParser = require('gettext-parser');
export interface TranslationStatus {
locale?: string;
languageName?: string;
translatorName: string;
percentDone: number;
untranslatedCount: number;
}
export type Translations = Record<string, string>; export type Translations = Record<string, string>;
export const removePoHeaderDate = async (filePath: string) => { export const removePoHeaderDate = async (filePath: string) => {