@remvst/localization 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { hideBin } from 'yargs/helpers';
4
+ import yargs from 'yargs';
5
+ import { combineJsonCommand } from './commands/combine-json';
6
+ import { googleTranslateCommand } from './commands/google-translate';
7
+ import { parseCsvCommand } from './commands/parse-csv';
8
+ import { toTypescriptCommand } from './commands/to-typescript';
9
+
10
+ async function main() {
11
+ await yargs(hideBin(process.argv))
12
+ .command('parse-csv', 'convert a CSV into a JSON', async (yargs) => {
13
+ const argv = await yargs
14
+ .option('in', {
15
+ type: 'string',
16
+ alias: 'i',
17
+ describe: 'Path to CSV to read from',
18
+ })
19
+ .option('out', {
20
+ type: 'string',
21
+ alias: 'o',
22
+ describe: 'Output file',
23
+ })
24
+ .option('languagesLineIndex', {
25
+ type: 'number',
26
+ alias: 'l',
27
+ describe: 'Index of the row containing the languages',
28
+ })
29
+ .demandOption('in')
30
+ .demandOption('out')
31
+ .demandOption('languagesLineIndex')
32
+ .argv
33
+
34
+ await parseCsvCommand(argv);
35
+ })
36
+ .command('combine-json', 'combine multiple JSON files', async (yargs) => {
37
+ const argv = await yargs
38
+ .option('main', {
39
+ type: 'string',
40
+ alias: 'i',
41
+ describe: 'JSON file to read from',
42
+ })
43
+ .option('out', {
44
+ type: 'string',
45
+ alias: 'o',
46
+ describe: 'Output JSON file',
47
+ })
48
+ .option('fallbackLocale', {
49
+ type: 'string',
50
+ describe: 'Default locale to backfill translations',
51
+ default: 'en',
52
+ })
53
+ .option('fallback', {
54
+ type: 'string',
55
+ alias: 'f',
56
+ describe: 'Fallback JSON file to read from',
57
+ })
58
+ .option('locale', {
59
+ type: 'string',
60
+ alias: 'l',
61
+ describe: 'Locale to include',
62
+ })
63
+ .array('fallback')
64
+ .array('locale')
65
+ .demandOption('main')
66
+ .demandOption('out')
67
+ .demandOption('locale')
68
+ .demandOption('fallback')
69
+ .argv
70
+
71
+ await combineJsonCommand(argv);
72
+ })
73
+ .command('to-typescript', 'converts a JSON file into a TypeScript file', async (yargs) => {
74
+ const argv = await yargs
75
+ .option('in', {
76
+ type: 'string',
77
+ alias: 'i',
78
+ describe: 'JSON file to read from',
79
+ })
80
+ .options('out', {
81
+ type: 'string',
82
+ alias: 'o',
83
+ describe: 'Output Typescript file',
84
+ })
85
+ .demandOption('in')
86
+ .demandOption('out')
87
+ .argv
88
+
89
+ await toTypescriptCommand(argv);
90
+ })
91
+ .command('google-translate', 'adds missing translations to a JSON file using Google translate', async (yargs) => {
92
+ const argv = await yargs
93
+ .option('in', {
94
+ type: 'string',
95
+ alias: 'i',
96
+ describe: 'JSON file to read from',
97
+ })
98
+ .option('out', {
99
+ type: 'string',
100
+ alias: 'o',
101
+ describe: 'Output JSON file',
102
+ })
103
+ .option('fallbackLocale', {
104
+ type: 'string',
105
+ describe: 'Default locale to backfill translations',
106
+ default: 'en',
107
+ })
108
+ .option('locale', {
109
+ type: 'string',
110
+ alias: 'l',
111
+ describe: 'Locale to include',
112
+ })
113
+ .array('fallback')
114
+ .array('locale')
115
+ .demandOption('in')
116
+ .demandOption('out')
117
+ .demandOption('locale')
118
+ .argv
119
+
120
+ await googleTranslateCommand(argv);
121
+ })
122
+ .argv;
123
+ }
124
+
125
+ main();
@@ -0,0 +1,119 @@
1
+ export type Key = string;
2
+ export type Locale = string;
3
+ export type LocalizedItem = Map<Locale, string>;
4
+
5
+ export class TranslationSet {
6
+
7
+ private readonly locales = new Set<Locale>();
8
+ private readonly translations = new Map<Key, LocalizedItem>();
9
+
10
+ add(
11
+ key: Key,
12
+ locale: Locale,
13
+ localization: string,
14
+ ) {
15
+ this.locales.add(locale);
16
+
17
+ if (!this.translations.has(key)) {
18
+ this.translations.set(key, new Map());
19
+ }
20
+
21
+ this.translations.get(key)!.set(locale, localization);
22
+ }
23
+
24
+ fromLocalization(
25
+ language: Locale,
26
+ localization: string,
27
+ ): LocalizedItem | null {
28
+ const search = localization.toLowerCase();
29
+ for (const localizedItem of this.translations.values()) {
30
+ if (localizedItem.get(language)?.toLowerCase() === search) {
31
+ return localizedItem;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+
37
+ toJSON() {
38
+ const res: {[key: string]: {[key: string]: string}} = {};
39
+ for (const [key, localizedItem] of this.translations.entries()) {
40
+ res[key] = {};
41
+ for (const [locale, translation] of localizedItem.entries()) {
42
+ res[key][locale] = translation;
43
+ }
44
+ }
45
+ return res;
46
+ }
47
+
48
+ static fromJSON(json: {[key: string]: {[key: string]: string}}): TranslationSet {
49
+ const res = new TranslationSet();
50
+ for (const [key, localizedItem] of Object.entries(json)) {
51
+ for (const [locale, translation] of Object.entries(localizedItem)) {
52
+ res.add(key, locale, translation);
53
+ }
54
+ }
55
+ return res;
56
+ }
57
+
58
+ toTypeScript(): string {
59
+ let generatedFileContent = '';
60
+
61
+ generatedFileContent += `export type Locale = ${Array.from(this.locales).map(key => JSON.stringify(key)).join(' | ')};\n\n`;
62
+ generatedFileContent += `export type LocalizedKey = ${Array.from(this.translations.keys()).map(key => JSON.stringify(key)).join(' | ')};\n\n`;
63
+ generatedFileContent += `export type LocalizationItem = {[key in Locale]?: string};\n\n`;
64
+
65
+ generatedFileContent += 'export const LOCALIZATION: {[key in LocalizedKey]: LocalizationItem} = {\n';
66
+
67
+ for (const [key, localizedItem] of this.translations.entries()) {
68
+ generatedFileContent += ` ${key}: {\n`;
69
+
70
+ for (const [locale, translation] of localizedItem.entries()) {
71
+ if (!translation) continue;
72
+ generatedFileContent += ` ${locale}: ${JSON.stringify(translation)},\n`;
73
+ }
74
+
75
+ generatedFileContent += ` },\n`;
76
+ }
77
+
78
+ generatedFileContent += '};\n\n';
79
+
80
+ generatedFileContent += `let LOCALE: Locale = 'en';\n\n`;
81
+
82
+ generatedFileContent += `export function setLocale(locale: string) {\n`;
83
+ generatedFileContent += ` LOCALE = locale.split('-')[0] as Locale\n`;
84
+ generatedFileContent += `}\n\n`;
85
+
86
+ generatedFileContent += `export function localize(key: LocalizedKey) {\n`;
87
+ generatedFileContent += ` const localizationItem: LocalizationItem = LOCALIZATION[key];\n`;
88
+ generatedFileContent += ` return localizationItem[LOCALE] || localizationItem.en;\n`;
89
+ generatedFileContent += `}\n`;
90
+
91
+ return generatedFileContent;
92
+ }
93
+
94
+ async applyFallbacks(
95
+ locales: Locale[],
96
+ fallback: (key: Key, locale: Locale, localizedItem: LocalizedItem) => Promise<string | null>,
97
+ ) {
98
+ for (const [key, localizedItem] of this.translations.entries()) {
99
+ for (const locale of locales) {
100
+ let translation = localizedItem.get(locale) || null;
101
+ if (translation) continue;
102
+
103
+ try {
104
+ translation = await fallback(key, locale, localizedItem);
105
+ } catch (err) {
106
+ console.error(err);
107
+ continue;
108
+ }
109
+
110
+ if (!translation) {
111
+ console.warn(`Unable to find fallback for ${key}`);
112
+ continue;
113
+ }
114
+
115
+ this.add(key, locale, translation);
116
+ }
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "ok": { "en": "Okay" },
3
+ "confirm": { "en": "Confirm" },
4
+ "cancel": { "en": "Cancel" },
5
+ "back": { "en": "Back" },
6
+ "play": { "en": "Play" },
7
+ "time": { "en": "Time" },
8
+ "backToMainMenu": { "en": "Back To Main Menu", "fr": "Retour au menu principal" }
9
+ }