diff --git a/packages/i18n/src/scripts/download.ts b/packages/i18n/src/scripts/download.ts index 07367f2fc4..f9450810e6 100644 --- a/packages/i18n/src/scripts/download.ts +++ b/packages/i18n/src/scripts/download.ts @@ -5,11 +5,15 @@ import path from 'node:path'; import { format } from 'prettier'; import { getAllProjectLanguages, getRemoteTranslations } from './api.js'; -import type { TranslationRes } from './utils.js'; +import { flattenTranslation, type TranslationRes } from './utils.js'; +const INDENT = 2; const RES_DIR = path.resolve(process.cwd(), 'src', 'resources'); -const countKeys = (obj: TranslationRes) => { +const countKeys = (obj: TranslationRes | null) => { + if (!obj) { + return 0; + } let count = 0; // eslint-disable-next-line @typescript-eslint/no-unused-vars Object.entries(obj).forEach(([_, value]) => { @@ -42,6 +46,12 @@ const getBaseTranslations = async (baseLanguage: { tag: string }) => { }; const main = async () => { + try { + fs.access(RES_DIR); + } catch (error) { + fs.mkdir(RES_DIR); + console.log('Create directory', RES_DIR); + } console.log('Loading project languages...'); const languages = await getAllProjectLanguages(); const baseLanguage = languages.find(language => language.base); @@ -58,11 +68,17 @@ const main = async () => { console.log(`Loading ${language.tag} translations...`); const translations = await getRemoteTranslations(language.tag); const keyNum = countKeys(translations); + const completeRate = Number((keyNum / baseKeyNum).toFixed(3)); + console.log( + `Load ${language.name} ${ + completeRate * 100 + }, %(${keyNum}/${baseKeyNum}) complete` + ); return { ...language, translations, - completeRate: keyNum / baseKeyNum, + completeRate, }; }) ); @@ -81,10 +97,10 @@ const main = async () => { { '// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.': '', - ...language.translations, + ...flattenTranslation(language.translations), }, null, - 4 + INDENT ) + '\n' ); }); @@ -92,7 +108,7 @@ const main = async () => { console.log('Generating meta data...'); const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. // Run \`yarn run download-resources\` to regenerate. - // To overwrite this, please overwrite download.ts script. + // If you need to update the code, please edit \`i18n/src/scripts/download.ts\` inside your project. ${availableLanguages .map( language => @@ -100,14 +116,16 @@ const main = async () => { language.tag }.json'` ) + .sort() .join('\n')} export const LOCALES = [ ${availableLanguages - // eslint-disable-next-line @typescript-eslint/no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- omit key .map(({ translations, ...language }) => JSON.stringify({ ...language, + // a trick to generate a string without quotation marks res: '__RES_PLACEHOLDER', }).replace( '"__RES_PLACEHOLDER"', @@ -124,7 +142,7 @@ const main = async () => { parser: 'typescript', singleQuote: true, trailingComma: 'es5', - tabWidth: 4, + tabWidth: INDENT, arrowParens: 'avoid', }) ); diff --git a/packages/i18n/src/scripts/utils.ts b/packages/i18n/src/scripts/utils.ts index c17e3446be..1eb9e58a47 100644 --- a/packages/i18n/src/scripts/utils.ts +++ b/packages/i18n/src/scripts/utils.ts @@ -1,3 +1,35 @@ export interface TranslationRes { [x: string]: string | TranslationRes; } + +/** + * Recursively flattens a JSON object using dot notation. + * + * NOTE: input must be an object as described by JSON spec. Arbitrary + * JS objects (e.g. {a: () => 42}) may result in unexpected output. + * MOREOVER, it removes keys with empty objects/arrays as value (see + * examples bellow). + * + * @example + * flattenTranslation({a: 1, b: [{c: 2, d: {e: 3}}, 4]}) + * // {a: 1, b.0.c: 2, b.0.d.e: 3, b.1: 4} + * flattenTranslation({a: 1, b: [{c: 2, d: {e: [true, false, {f: 1}]}}]}) + * // {a: 1, b.0.c: 2, b.0.d.e.0: true, b.0.d.e.1: false, b.0.d.e.2.f: 1} + * flattenTranslation({a: 1, b: [], c: {}}) + * // {a: 1} + * + * @param obj item to be flattened + */ +export const flattenTranslation = ( + obj: string | TranslationRes, + path?: string +): TranslationRes => { + if (!(obj instanceof Object)) return { [path ?? '']: obj }; + + return Object.keys(obj).reduce((output, key) => { + return { + ...output, + ...flattenTranslation(obj[key], path ? path + '.' + key : key), + }; + }, {}); +};