generated from OlegWock/webpack-react-web-extension-template
-
-
Notifications
You must be signed in to change notification settings - Fork 45
/
translations-manager.ts
172 lines (156 loc) · 8.11 KB
/
translations-manager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import { join } from 'path';
import { writeFileSync, readFileSync, readdirSync, rmSync, existsSync, mkdirSync } from 'fs';
import { get, set } from 'lodash';
const objectDeepKeys = (obj: object): string[] => {
return Object.keys(obj)
// @ts-ignore I copied this from SO and don't know how to type it
.filter(key => obj[key] instanceof Object)
// @ts-ignore I copied this from SO and don't know how to type it
.map(key => objectDeepKeys(obj[key]).map(k => `${key}.${k}`))
.reduce((x, y) => x.concat(y), Object.keys(obj))
};
const loadJsonFile = (fname: string): object => {
const raw = readFileSync(fname, 'utf-8');
return JSON.parse(raw);
};
const saveJsonFile = (fname: string, obj: object) => {
writeFileSync(fname, JSON.stringify(obj, null, 4));
};
const transformLocaleNameForChrome = (lang: string) => {
if (lang.includes('-')) {
const tokens = lang.split('-');
tokens[1] = tokens[1].toUpperCase();
return tokens.join('_');
}
return lang;
}
const main = async () => {
const args = process.argv.slice(2);
const translationFiles = readdirSync(TRANSLATIONS_FOLDER).filter(fn => fn.endsWith('.json'));
const translations = translationFiles.map(fn => fn.replace('.json', '')).filter(fn => !fn.endsWith('-missing'));
console.log('Found translation files', translations.join(', '));
const defaultTranslation = loadJsonFile(join(TRANSLATIONS_FOLDER, `${DEFAULT_LANGUAGE}.json`));
const defaultKeys = objectDeepKeys(defaultTranslation).filter(k => k !== 'translation');
const results: Record<string, { missing: string[], excessive: string[] }> = {};
translations.filter(t => t !== DEFAULT_LANGUAGE).forEach(lang => {
const data = loadJsonFile(join(TRANSLATIONS_FOLDER, `${lang}.json`));
const keys = objectDeepKeys(data).filter(k => k !== 'translation');
const missingKeys = defaultKeys.filter(k => !keys.includes(k));
const excessiveKeys = keys.filter(k => !defaultKeys.includes(k));
results[lang] = {
missing: missingKeys,
excessive: excessiveKeys,
}
});
if (args[0] === 'check-missing') {
console.log('Checking for keys missing in lang-missing.json files.');
let exitWithError = false;
Object.entries(results).forEach(([lang, result]) => {
if (result.excessive.length !== 0) {
console.log(`❌ Language ${lang} has excessive keys (${result.excessive.slice(0, 3).join(', ')}...)`);
return;
}
if (result.missing.length === 0) {
console.log(`✅ Language ${lang} all good!`);
} else {
if (!existsSync(join(TRANSLATIONS_FOLDER, `${lang}-missing.json`))) {
console.log(`❌ Language ${lang} has missing keys, but doesn't have ${lang}-missing.json file`);
exitWithError = true;
return;
}
const missingFile = loadJsonFile(join(TRANSLATIONS_FOLDER, `${lang}-missing.json`));
const notAllMissingKeysInFile = result.missing.some(k => get(missingFile, k) === undefined);
if (notAllMissingKeysInFile) {
console.log(`❌ Language ${lang} has missing keys which aren't found in ${lang}-missing.json file`);
exitWithError = true;
} else {
console.log(`✅ Language ${lang} all good!`);
}
}
});
if (exitWithError) {
console.log('🚨 Problems with files were detected, run `yarn translations:extract` to fix them.')
process.exit(1);
}
} else if (args[0] === 'remove-excessive') {
Object.entries(results).filter(([lang, { excessive }]) => excessive.length !== 0).forEach(([lang, { excessive }]) => {
console.log('Removing excessive strings from', lang);
const filename = join(TRANSLATIONS_FOLDER, `${lang}.json`);
const original = loadJsonFile(filename);
excessive.forEach(k => set(original, k, undefined));
saveJsonFile(filename, original);
});
} else if (args[0] === 'extract-untranslated') {
Object.entries(results).filter(([lang, { missing }]) => missing.length !== 0).forEach(([lang, { missing }]) => {
console.log('Extracting missing strings from', lang);
const newData = {};
missing.forEach(key => {
const defaultValue = get(defaultTranslation, key);
set(newData, key, defaultValue);
});
saveJsonFile(join(TRANSLATIONS_FOLDER, `${lang}-missing.json`), newData);
});
} else if (args[0] === 'merge-back') {
const langToMerge = args[1];
if (!langToMerge) {
console.log('❌ Specify which language file to merge: yarn translations:merge <lang>');
process.exit(1);
}
const filesToMerge = translationFiles.filter(fn => fn === `${langToMerge}-missing.json`);
console.log('Going to merge', filesToMerge.join(', '));
filesToMerge.forEach(mergeFilename => {
const lang = mergeFilename.replace('-missing.json', '');
const filename = join(TRANSLATIONS_FOLDER, `${lang}.json`);
const original = loadJsonFile(filename);
const toMerge = loadJsonFile(join(TRANSLATIONS_FOLDER, mergeFilename));
const keysToMerge = objectDeepKeys(toMerge).filter(k => typeof get(toMerge, k) === 'string');
keysToMerge.forEach(k => {
set(original, k, get(toMerge, k));
});
saveJsonFile(filename, original);
console.log('Merged', lang);
rmSync(join(TRANSLATIONS_FOLDER, mergeFilename));
});
} else if (args[0] === 'generate-locales') {
console.log('Generating locales for', FINISHED_TRANSLATIONS.join(', '));
const localesKeysToTranslationKeys = [
['appName.message', 'translation.appName'],
['appDescription.message', 'translation.appDescription'],
['appActionTitle.message', 'translation.appActionTitle'],
];
FINISHED_TRANSLATIONS.forEach(lang => {
const correctedLang = transformLocaleNameForChrome(lang);
const filename = join(TRANSLATIONS_FOLDER, `${lang}.json`);
const original = loadJsonFile(filename);
if (!existsSync(join(__dirname, `src/_locales/${correctedLang}`))) {
mkdirSync(join(__dirname, `src/_locales/${correctedLang}`));
}
const data = localesKeysToTranslationKeys.reduce((obj, [lk, tk]) => {
return set(obj, lk, get(original, tk));
}, {});
saveJsonFile(join(__dirname, `src/_locales/${correctedLang}/messages.json`), data)
});
} else if (args[0] === 'generate-safari') {
console.log('Generating Safari locales for', FINISHED_TRANSLATIONS.join(', '));
const resultDir = join(__dirname, 'safari-app/anori/Shared (App)/Resources/locales');
const htmlFile = readFileSync(join(__dirname, 'safari-app/anori/Shared (App)/Resources/Main.html'), { encoding: 'utf-8' });
const keys = [...htmlFile.matchAll(/data-t-key=(?:"([^"]+)"|'([^']+)')/gmi)].map(m => (m[1] || m[2]));
console.log('Keys to copy:', keys);
FINISHED_TRANSLATIONS.forEach(lang => {
// const correctedLang = transformLocaleNameForChrome(lang);
const filename = join(TRANSLATIONS_FOLDER, `${lang}.json`);
const original = loadJsonFile(filename);
const data = keys.reduce((obj, key) => {
console.log(`[${lang}]`, 'Setting', key, 'to value', get(original, key));
return set(obj, key, get(original, key));
}, {});
saveJsonFile(join(resultDir, `${lang}.json`), data)
});
} else {
console.error('Unknown command', args[0]);
}
};
const DEFAULT_LANGUAGE = 'en';
const TRANSLATIONS_FOLDER = join(__dirname, 'src/translations');
const FINISHED_TRANSLATIONS = ['en', 'uk', 'it', 'de', 'fr', 'es', 'th', 'zh-cn', 'ru', 'ar', 'pt-br'];
main();