@remvst/localization 1.0.2 → 2.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 CHANGED
@@ -1,125 +1,101 @@
1
1
  #!/usr/bin/env node
2
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';
3
+ import yargs from "yargs";
4
+ import { hideBin } from "yargs/helpers";
5
+ import { findStringsCommand } from "./commands/find-strings";
6
+ import { localize } from "./commands/localize";
7
+ import { toTypescriptCommand } from "./commands/to-typescript";
9
8
 
10
9
  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
10
+ await yargs(hideBin(process.argv))
11
+ .command(
12
+ "find-strings",
13
+ "Finds all localizable strings in a source file",
14
+ async (yargs) => {
15
+ const argv = await yargs
16
+ .option("in", {
17
+ type: "string",
18
+ alias: "i",
19
+ describe: "Folder to read from",
20
+ })
21
+ .options("out", {
22
+ type: "string",
23
+ alias: "o",
24
+ describe: "Output JSON file",
25
+ })
26
+ .options("localize-function-name", {
27
+ type: "string",
28
+ describe: "Name of the function used for localization",
29
+ })
30
+ .demandOption("in")
31
+ .demandOption("out")
32
+ .demandOption("localize-function-name").argv;
33
33
 
34
- await parseCsvCommand(argv);
34
+ await findStringsCommand(argv);
35
+ },
36
+ )
37
+ .command("localize", "Creates a localization JSON file", async (yargs) => {
38
+ const argv = await yargs
39
+ .option("source-json", {
40
+ type: "string",
41
+ describe: "File that contains all strings in the original language",
35
42
  })
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);
43
+ .option("source-locale", {
44
+ type: "string",
45
+ describe: "Locale to translate from",
72
46
  })
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);
47
+ .options("destination-json", {
48
+ type: "string",
49
+ alias: "o",
50
+ describe: "Output JSON file",
90
51
  })
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);
52
+ .option("destination-locale", {
53
+ type: "string",
54
+ describe: "Locale to translate to",
55
+ })
56
+ .option("context", {
57
+ type: "string",
58
+ describe: "Context to provide to the translation engine",
121
59
  })
122
- .argv;
60
+ .option("engine", {
61
+ type: "string",
62
+ choices: ["gpt", "google"],
63
+ default: "google",
64
+ describe: "Translation engine to use",
65
+ })
66
+ .demandOption("source-json")
67
+ .demandOption("source-locale")
68
+ .demandOption("destination-json")
69
+ .demandOption("destination-locale").argv;
70
+
71
+ await localize(argv);
72
+ })
73
+ .command(
74
+ "to-typescript",
75
+ "Creates a TypeScript file that can be then imported to localize a project",
76
+ async (yargs) => {
77
+ const argv = await yargs
78
+ .option("default-localization", {
79
+ type: "string",
80
+ alias: "i",
81
+ describe: "Default localization JSON file",
82
+ })
83
+ .options("out", {
84
+ type: "string",
85
+ alias: "o",
86
+ describe: "Output Typescript file",
87
+ })
88
+ .options("localize-function-name", {
89
+ type: "string",
90
+ describe: "Name of the function used for localization",
91
+ })
92
+ .demandOption("default-localization")
93
+ .demandOption("out")
94
+ .demandOption("localize-function-name").argv;
95
+
96
+ await toTypescriptCommand(argv);
97
+ },
98
+ ).argv;
123
99
  }
124
100
 
125
101
  main();
@@ -0,0 +1,3 @@
1
+ console.log(i18n("hello world"));
2
+ console.log(i18n("how is it going?"));
3
+ console.log(i18n(`how do you do?`));
package/tsconfig.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "es2020",
4
- "module": "commonjs",
5
- "declaration": true,
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "allowJs": false,
9
- "outDir": "./lib",
10
- "skipLibCheck": true
11
- },
12
- "include": ["./src/**/*"],
13
- "exclude": ["node_modules"]
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "allowJs": false,
9
+ "outDir": "./lib",
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["./src/**/*"],
13
+ "exclude": ["node_modules"]
14
14
  }
@@ -1,7 +0,0 @@
1
- export declare function combineJsonCommand(options: {
2
- main: string;
3
- out: string;
4
- locale: string[];
5
- fallbackLocale: string;
6
- fallback: string[];
7
- }): Promise<void>;
@@ -1,35 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.combineJsonCommand = void 0;
7
- const fs_1 = require("fs");
8
- const translation_set_1 = require("../model/translation-set");
9
- const path_1 = __importDefault(require("path"));
10
- async function combineJsonCommand(options) {
11
- const fallbackTranslations = [];
12
- for (const fallbackPath of options.fallback || []) {
13
- const sourceJson = JSON.parse(await fs_1.promises.readFile(fallbackPath, 'utf-8'));
14
- const translationSet = translation_set_1.TranslationSet.fromJSON(sourceJson);
15
- fallbackTranslations.push(translationSet);
16
- }
17
- const mainJson = JSON.parse(await fs_1.promises.readFile(options.main, 'utf-8'));
18
- const outTranslations = translation_set_1.TranslationSet.fromJSON(mainJson);
19
- await outTranslations.applyFallbacks(options.locale || ['en'], async (key, locale, item) => {
20
- const translationInFallbackLocale = item.get(options.fallbackLocale);
21
- if (!translationInFallbackLocale)
22
- return null;
23
- for (const fallbackSet of fallbackTranslations) {
24
- if (!translationInFallbackLocale)
25
- continue;
26
- const fromFallbackLocale = fallbackSet.fromLocalization(options.fallbackLocale, translationInFallbackLocale)?.get(locale);
27
- if (fromFallbackLocale)
28
- return fromFallbackLocale;
29
- }
30
- return null;
31
- });
32
- await fs_1.promises.mkdir(path_1.default.dirname(options.out), { recursive: true });
33
- await fs_1.promises.writeFile(options.out, JSON.stringify(outTranslations.toJSON(), null, 4));
34
- }
35
- exports.combineJsonCommand = combineJsonCommand;
@@ -1,6 +0,0 @@
1
- export declare function googleTranslateCommand(options: {
2
- in: string;
3
- out: string;
4
- locale: string[];
5
- fallbackLocale: string;
6
- }): Promise<void>;
@@ -1,25 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.googleTranslateCommand = void 0;
7
- const fs_1 = require("fs");
8
- const translation_set_1 = require("../model/translation-set");
9
- const google_translate_api_1 = require("@vitalets/google-translate-api");
10
- const path_1 = __importDefault(require("path"));
11
- async function googleTranslateCommand(options) {
12
- const mainJson = JSON.parse(await fs_1.promises.readFile(options.in, 'utf-8'));
13
- const outTranslations = translation_set_1.TranslationSet.fromJSON(mainJson);
14
- await outTranslations.applyFallbacks(options.locale || ['en'], async (_, locale, item) => {
15
- const translationInFallbackLocale = item.get(options.fallbackLocale);
16
- if (!translationInFallbackLocale)
17
- return null;
18
- console.log(`Translating ${JSON.stringify(translationInFallbackLocale)} to ${locale}`);
19
- const translationResult = await (0, google_translate_api_1.translate)(translationInFallbackLocale, { from: options.fallbackLocale, to: locale });
20
- return translationResult?.text;
21
- });
22
- await fs_1.promises.mkdir(path_1.default.dirname(options.out), { recursive: true });
23
- await fs_1.promises.writeFile(options.out, JSON.stringify(outTranslations.toJSON(), null, 4));
24
- }
25
- exports.googleTranslateCommand = googleTranslateCommand;
@@ -1,7 +0,0 @@
1
- import { TranslationSet } from "../model/translation-set";
2
- export declare function parseCsv(path: string, languagesLine: number): Promise<TranslationSet>;
3
- export declare function parseCsvCommand(options: {
4
- in: string;
5
- out: string;
6
- languagesLineIndex: number;
7
- }): Promise<void>;
@@ -1,36 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.parseCsvCommand = exports.parseCsv = void 0;
7
- const fs_1 = require("fs");
8
- const translation_set_1 = require("../model/translation-set");
9
- const path_1 = __importDefault(require("path"));
10
- async function parseCsv(path, languagesLine) {
11
- const csvContent = await fs_1.promises.readFile(path, 'utf-8');
12
- const csvLines = csvContent.split('\n');
13
- const res = new translation_set_1.TranslationSet();
14
- const ietfLine = csvLines[languagesLine];
15
- const languagesIndices = new Map();
16
- const ietfColumns = ietfLine.split(',');
17
- for (let i = 2; i < ietfColumns.length; i++) {
18
- const locale = ietfColumns[i].slice(0, 2); // only grab the first two chars to identify the language
19
- languagesIndices.set(locale, i);
20
- }
21
- for (const line of csvLines) {
22
- const columns = line.split(',');
23
- const key = columns[0];
24
- for (const [locale, columnIndex] of languagesIndices.entries()) {
25
- res.add(key, locale, columns[columnIndex]);
26
- }
27
- }
28
- return res;
29
- }
30
- exports.parseCsv = parseCsv;
31
- async function parseCsvCommand(options) {
32
- const polyglotTranslations = await parseCsv(options.in, options.languagesLineIndex);
33
- await fs_1.promises.mkdir(path_1.default.dirname(options.out), { recursive: true });
34
- await fs_1.promises.writeFile(options.out, JSON.stringify(polyglotTranslations, null, 4));
35
- }
36
- exports.parseCsvCommand = parseCsvCommand;
@@ -1,21 +0,0 @@
1
- export type Key = string;
2
- export type Locale = string;
3
- export type LocalizedItem = Map<Locale, string>;
4
- export declare class TranslationSet {
5
- private readonly locales;
6
- private readonly translations;
7
- add(key: Key, locale: Locale, localization: string): void;
8
- fromLocalization(language: Locale, localization: string): LocalizedItem | null;
9
- toJSON(): {
10
- [key: string]: {
11
- [key: string]: string;
12
- };
13
- };
14
- static fromJSON(json: {
15
- [key: string]: {
16
- [key: string]: string;
17
- };
18
- }): TranslationSet;
19
- toTypeScript(): string;
20
- applyFallbacks(locales: Locale[], fallback: (key: Key, locale: Locale, localizedItem: LocalizedItem) => Promise<string | null>): Promise<void>;
21
- }
@@ -1,93 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TranslationSet = void 0;
4
- class TranslationSet {
5
- constructor() {
6
- this.locales = new Set();
7
- this.translations = new Map();
8
- }
9
- add(key, locale, localization) {
10
- this.locales.add(locale);
11
- if (!this.translations.has(key)) {
12
- this.translations.set(key, new Map());
13
- }
14
- this.translations.get(key).set(locale, localization);
15
- }
16
- fromLocalization(language, localization) {
17
- const search = localization.toLowerCase();
18
- for (const localizedItem of this.translations.values()) {
19
- if (localizedItem.get(language)?.toLowerCase() === search) {
20
- return localizedItem;
21
- }
22
- }
23
- return null;
24
- }
25
- toJSON() {
26
- const res = {};
27
- for (const [key, localizedItem] of this.translations.entries()) {
28
- res[key] = {};
29
- for (const [locale, translation] of localizedItem.entries()) {
30
- res[key][locale] = translation;
31
- }
32
- }
33
- return res;
34
- }
35
- static fromJSON(json) {
36
- const res = new TranslationSet();
37
- for (const [key, localizedItem] of Object.entries(json)) {
38
- for (const [locale, translation] of Object.entries(localizedItem)) {
39
- res.add(key, locale, translation);
40
- }
41
- }
42
- return res;
43
- }
44
- toTypeScript() {
45
- let generatedFileContent = '';
46
- generatedFileContent += `export type Locale = ${Array.from(this.locales).map(key => JSON.stringify(key)).join(' | ')};\n\n`;
47
- generatedFileContent += `export type LocalizedKey = ${Array.from(this.translations.keys()).map(key => JSON.stringify(key)).join(' | ')};\n\n`;
48
- generatedFileContent += `export type LocalizationItem = {[key in Locale]?: string};\n\n`;
49
- generatedFileContent += 'export const LOCALIZATION: {[key in LocalizedKey]: LocalizationItem} = {\n';
50
- for (const [key, localizedItem] of this.translations.entries()) {
51
- generatedFileContent += ` ${JSON.stringify(key)}: {\n`;
52
- for (const [locale, translation] of localizedItem.entries()) {
53
- if (!translation)
54
- continue;
55
- generatedFileContent += ` ${JSON.stringify(locale)}: ${JSON.stringify(translation)},\n`;
56
- }
57
- generatedFileContent += ` },\n`;
58
- }
59
- generatedFileContent += '};\n\n';
60
- generatedFileContent += `let LOCALE: Locale = 'en';\n\n`;
61
- generatedFileContent += `export function setLocale(locale: string) {\n`;
62
- generatedFileContent += ` LOCALE = locale.split('-')[0] as Locale\n`;
63
- generatedFileContent += `}\n\n`;
64
- generatedFileContent += `export function localize(key: LocalizedKey) {\n`;
65
- generatedFileContent += ` const localizationItem: LocalizationItem = LOCALIZATION[key];\n`;
66
- generatedFileContent += ` if (!localizationItem) return key;\n`;
67
- generatedFileContent += ` return localizationItem[LOCALE] || localizationItem.en;\n`;
68
- generatedFileContent += `}\n`;
69
- return generatedFileContent;
70
- }
71
- async applyFallbacks(locales, fallback) {
72
- for (const [key, localizedItem] of this.translations.entries()) {
73
- for (const locale of locales) {
74
- let translation = localizedItem.get(locale) || null;
75
- if (translation)
76
- continue;
77
- try {
78
- translation = await fallback(key, locale, localizedItem);
79
- }
80
- catch (err) {
81
- console.error(err);
82
- continue;
83
- }
84
- if (!translation) {
85
- console.warn(`Unable to find fallback for ${key}`);
86
- continue;
87
- }
88
- this.add(key, locale, translation);
89
- }
90
- }
91
- }
92
- }
93
- exports.TranslationSet = TranslationSet;
@@ -1,41 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import { TranslationSet, Key, Locale, LocalizedItem } from '../model/translation-set';
3
- import path from 'path';
4
-
5
- export async function combineJsonCommand(options: {
6
- main: string,
7
- out: string,
8
- locale: string[],
9
- fallbackLocale: string,
10
- fallback: string[],
11
- }) {
12
- const fallbackTranslations: TranslationSet[] = [];
13
-
14
- for (const fallbackPath of options.fallback || []) {
15
- const sourceJson = JSON.parse(await fs.readFile(fallbackPath, 'utf-8'));
16
- const translationSet = TranslationSet.fromJSON(sourceJson);
17
- fallbackTranslations.push(translationSet);
18
- }
19
-
20
- const mainJson = JSON.parse(await fs.readFile(options.main, 'utf-8'));
21
- const outTranslations = TranslationSet.fromJSON(mainJson);
22
-
23
- await outTranslations.applyFallbacks(
24
- options.locale || ['en'],
25
- async (key: Key, locale: Locale, item: LocalizedItem) => {
26
- const translationInFallbackLocale = item.get(options.fallbackLocale);
27
- if (!translationInFallbackLocale) return null;
28
-
29
- for (const fallbackSet of fallbackTranslations) {
30
- if (!translationInFallbackLocale) continue;
31
- const fromFallbackLocale = fallbackSet.fromLocalization(options.fallbackLocale, translationInFallbackLocale)?.get(locale);
32
- if (fromFallbackLocale) return fromFallbackLocale;
33
- }
34
-
35
- return null;
36
- }
37
- );
38
-
39
- await fs.mkdir(path.dirname(options.out), { recursive: true });
40
- await fs.writeFile(options.out, JSON.stringify(outTranslations.toJSON(), null, 4));
41
- }
@@ -1,34 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import { Key, Locale, LocalizedItem, TranslationSet } from "../model/translation-set";
3
- import { translate } from '@vitalets/google-translate-api';
4
- import path from 'path';
5
-
6
- export async function googleTranslateCommand(options: {
7
- in: string,
8
- out: string,
9
- locale: string[],
10
- fallbackLocale: string,
11
- }) {
12
- const mainJson = JSON.parse(await fs.readFile(options.in, 'utf-8'));
13
- const outTranslations = TranslationSet.fromJSON(mainJson);
14
-
15
- await outTranslations.applyFallbacks(
16
- options.locale || ['en'],
17
- async (_: Key, locale: Locale, item: LocalizedItem) => {
18
- const translationInFallbackLocale = item.get(options.fallbackLocale);
19
- if (!translationInFallbackLocale) return null;
20
-
21
- console.log(`Translating ${JSON.stringify(translationInFallbackLocale)} to ${locale}`);
22
-
23
- const translationResult = await translate(
24
- translationInFallbackLocale,
25
- { from: options.fallbackLocale, to: locale },
26
- );
27
-
28
- return translationResult?.text;
29
- }
30
- );
31
-
32
- await fs.mkdir(path.dirname(options.out), { recursive: true });
33
- await fs.writeFile(options.out, JSON.stringify(outTranslations.toJSON(), null, 4));
34
- }
@@ -1,43 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import { TranslationSet } from "../model/translation-set";
3
- import path from 'path';
4
-
5
- export async function parseCsv(
6
- path: string,
7
- languagesLine: number,
8
- ): Promise<TranslationSet> {
9
- const csvContent = await fs.readFile(path, 'utf-8');
10
- const csvLines = csvContent.split('\n');
11
-
12
- const res = new TranslationSet();
13
-
14
- const ietfLine = csvLines[languagesLine];
15
- const languagesIndices = new Map<string, number>();
16
- const ietfColumns = ietfLine.split(',');
17
- for (let i = 2 ; i < ietfColumns.length ; i++) {
18
- const locale = ietfColumns[i].slice(0, 2); // only grab the first two chars to identify the language
19
- languagesIndices.set(locale, i);
20
- }
21
-
22
- for (const line of csvLines) {
23
- const columns = line.split(',');
24
-
25
- const key = columns[0];
26
-
27
- for (const [locale, columnIndex] of languagesIndices.entries()) {
28
- res.add(key, locale, columns[columnIndex]);
29
- }
30
- }
31
-
32
- return res;
33
- }
34
-
35
- export async function parseCsvCommand(options: {
36
- in: string,
37
- out: string,
38
- languagesLineIndex: number,
39
- }) {
40
- const polyglotTranslations = await parseCsv(options.in!, options.languagesLineIndex);
41
- await fs.mkdir(path.dirname(options.out), { recursive: true });
42
- await fs.writeFile(options.out!, JSON.stringify(polyglotTranslations, null, 4));
43
- }