fix: flatten i18n keys (#2483)

This commit is contained in:
Whitewater
2023-05-22 01:08:43 -07:00
committed by GitHub
parent 8aab1d6459
commit fe5be0cb47
2 changed files with 58 additions and 8 deletions

View File

@@ -5,11 +5,15 @@ import path from 'node:path';
import { format } from 'prettier'; import { format } from 'prettier';
import { getAllProjectLanguages, getRemoteTranslations } from './api.js'; 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 RES_DIR = path.resolve(process.cwd(), 'src', 'resources');
const countKeys = (obj: TranslationRes) => { const countKeys = (obj: TranslationRes | null) => {
if (!obj) {
return 0;
}
let count = 0; let count = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.entries(obj).forEach(([_, value]) => { Object.entries(obj).forEach(([_, value]) => {
@@ -42,6 +46,12 @@ const getBaseTranslations = async (baseLanguage: { tag: string }) => {
}; };
const main = async () => { 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...'); console.log('Loading project languages...');
const languages = await getAllProjectLanguages(); const languages = await getAllProjectLanguages();
const baseLanguage = languages.find(language => language.base); const baseLanguage = languages.find(language => language.base);
@@ -58,11 +68,17 @@ const main = async () => {
console.log(`Loading ${language.tag} translations...`); console.log(`Loading ${language.tag} translations...`);
const translations = await getRemoteTranslations(language.tag); const translations = await getRemoteTranslations(language.tag);
const keyNum = countKeys(translations); const keyNum = countKeys(translations);
const completeRate = Number((keyNum / baseKeyNum).toFixed(3));
console.log(
`Load ${language.name} ${
completeRate * 100
}, %(${keyNum}/${baseKeyNum}) complete`
);
return { return {
...language, ...language,
translations, translations,
completeRate: keyNum / baseKeyNum, completeRate,
}; };
}) })
); );
@@ -81,10 +97,10 @@ const main = async () => {
{ {
'// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.': '// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.':
'', '',
...language.translations, ...flattenTranslation(language.translations),
}, },
null, null,
4 INDENT
) + '\n' ) + '\n'
); );
}); });
@@ -92,7 +108,7 @@ const main = async () => {
console.log('Generating meta data...'); console.log('Generating meta data...');
const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
// Run \`yarn run download-resources\` to regenerate. // 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 ${availableLanguages
.map( .map(
language => language =>
@@ -100,14 +116,16 @@ const main = async () => {
language.tag language.tag
}.json'` }.json'`
) )
.sort()
.join('\n')} .join('\n')}
export const LOCALES = [ export const LOCALES = [
${availableLanguages ${availableLanguages
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars -- omit key
.map(({ translations, ...language }) => .map(({ translations, ...language }) =>
JSON.stringify({ JSON.stringify({
...language, ...language,
// a trick to generate a string without quotation marks
res: '__RES_PLACEHOLDER', res: '__RES_PLACEHOLDER',
}).replace( }).replace(
'"__RES_PLACEHOLDER"', '"__RES_PLACEHOLDER"',
@@ -124,7 +142,7 @@ const main = async () => {
parser: 'typescript', parser: 'typescript',
singleQuote: true, singleQuote: true,
trailingComma: 'es5', trailingComma: 'es5',
tabWidth: 4, tabWidth: INDENT,
arrowParens: 'avoid', arrowParens: 'avoid',
}) })
); );

View File

@@ -1,3 +1,35 @@
export interface TranslationRes { export interface TranslationRes {
[x: string]: string | 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),
};
}, {});
};