gettext-universal 1.0.13 → 1.0.14
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/build/bin/gettext-universal.d.ts +3 -0
- package/build/bin/gettext-universal.d.ts.map +1 -0
- package/build/bin/gettext-universal.js +38 -0
- package/build/bin/po2js.d.ts +3 -0
- package/build/bin/po2js.d.ts.map +1 -0
- package/build/bin/po2js.js +15 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +2 -0
- package/build/src/config.d.ts +23 -0
- package/build/src/config.d.ts.map +1 -0
- package/build/src/config.js +39 -0
- package/build/src/events.d.ts +4 -0
- package/build/src/events.d.ts.map +1 -0
- package/build/src/events.js +4 -0
- package/build/src/po2js.d.ts +17 -0
- package/build/src/po2js.d.ts.map +1 -0
- package/build/src/po2js.js +42 -0
- package/build/src/scanner.d.ts +53 -0
- package/build/src/scanner.d.ts.map +1 -0
- package/build/src/scanner.js +135 -0
- package/build/src/translate-context.d.ts +3 -0
- package/build/src/translate-context.d.ts.map +1 -0
- package/build/src/translate-context.js +3 -0
- package/build/src/translate.d.ts +16 -0
- package/build/src/translate.d.ts.map +1 -0
- package/build/src/translate.js +77 -0
- package/build/src/use-translate-expo.d.ts +3 -0
- package/build/src/use-translate-expo.d.ts.map +1 -0
- package/build/src/use-translate-expo.js +26 -0
- package/build/src/with-translate.d.ts +6 -0
- package/build/src/with-translate.d.ts.map +1 -0
- package/build/src/with-translate.js +33 -0
- package/package.json +17 -4
- package/.github/dependabot.yml +0 -9
- package/bin/po2js.js +0 -20
- package/peak_flow.yml +0 -4
- package/spec/gettext-universal.spec.js +0 -41
- package/spec/support/jasmine.js +0 -14
- package/src/config.js +0 -39
- package/src/events.js +0 -5
- package/src/po2js.js +0 -43
- package/src/scanner.js +0 -147
- package/src/translate.js +0 -77
- package/src/use-translate-expo.js +0 -79
- package/test.po +0 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gettext-universal.d.ts","sourceRoot":"","sources":["../../bin/gettext-universal.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import Scanner from "../src/scanner.js";
|
|
3
|
+
const processArgs = process.argv.slice(2);
|
|
4
|
+
const extensions = [];
|
|
5
|
+
const files = [];
|
|
6
|
+
const ignores = [];
|
|
7
|
+
let directory, output;
|
|
8
|
+
for (let i = 0; i < processArgs.length; i++) {
|
|
9
|
+
const arg = processArgs[i];
|
|
10
|
+
if (arg == "--directory") {
|
|
11
|
+
directory = processArgs[++i];
|
|
12
|
+
}
|
|
13
|
+
else if (arg == "--extension") {
|
|
14
|
+
extensions.push(processArgs[++i]);
|
|
15
|
+
}
|
|
16
|
+
else if (arg == "--files") {
|
|
17
|
+
while (i < processArgs.length - 1) {
|
|
18
|
+
const file = processArgs[++i];
|
|
19
|
+
if (!file)
|
|
20
|
+
throw new Error("No file found?");
|
|
21
|
+
files.push(file);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (arg == "--ignore") {
|
|
25
|
+
ignores.push(processArgs[++i]);
|
|
26
|
+
}
|
|
27
|
+
else if (arg == "--output") {
|
|
28
|
+
output = processArgs[++i];
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (extensions.length == 0) {
|
|
35
|
+
extensions.push(".js", ".cjs", ".mjs", ".jsx", ".tsx");
|
|
36
|
+
}
|
|
37
|
+
const scanner = new Scanner({ directory, extensions, files, ignores, output });
|
|
38
|
+
await scanner.scan();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"po2js.d.ts","sourceRoot":"","sources":["../../bin/po2js.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import Po2Js from "../src/po2js.js";
|
|
3
|
+
const processArgs = process.argv.slice(2);
|
|
4
|
+
let directory;
|
|
5
|
+
for (let i = 0; i < processArgs.length; i++) {
|
|
6
|
+
const arg = processArgs[i];
|
|
7
|
+
if (arg == "--directory") {
|
|
8
|
+
directory = processArgs[++i];
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const po2Js = new Po2Js({ directory });
|
|
15
|
+
await po2Js.run();
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":"AAAA,mBAAa,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export default config;
|
|
2
|
+
declare const config: Config;
|
|
3
|
+
declare class Config {
|
|
4
|
+
/** @type {Record<string, Record<string, any>>} */
|
|
5
|
+
locales: Record<string, Record<string, any>>;
|
|
6
|
+
loadTranslationsFromRequireContext(requireContext: any): void;
|
|
7
|
+
getFallbacks(): string[];
|
|
8
|
+
getLocale(): string;
|
|
9
|
+
getLocales(): Record<string, Record<string, any>>;
|
|
10
|
+
/**
|
|
11
|
+
* @param {string[]} fallbacks
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
setFallbacks(fallbacks: string[]): void;
|
|
15
|
+
fallbacks: string[];
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} locale
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
20
|
+
setLocale(locale: string): void;
|
|
21
|
+
locale: string;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.js"],"names":[],"mappings":";AA+CA,6BAA2B;AA3C3B;IAEI,kDAAkD;IAClD,SADW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAC7B;IAGnB,8DAaC;IAED,yBAAwC;IACxC,oBAAkC;IAClC,kDAAoC;IAEpC;;;OAGG;IACH,wBAHW,MAAM,EAAE,GACN,IAAI,CAIhB;IADC,oBAA0B;IAG5B;;;OAGG;IACH,kBAHW,MAAM,GACJ,IAAI,CAKhB;IAFC,eAAoB;CAGvB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import events from "./events.js";
|
|
3
|
+
class Config {
|
|
4
|
+
constructor() {
|
|
5
|
+
/** @type {Record<string, Record<string, any>>} */
|
|
6
|
+
this.locales = {};
|
|
7
|
+
}
|
|
8
|
+
loadTranslationsFromRequireContext(requireContext) {
|
|
9
|
+
for (const localeFile of requireContext.keys()) {
|
|
10
|
+
const match = localeFile.match(/([a-z]{2}).js$/);
|
|
11
|
+
if (!match) {
|
|
12
|
+
throw new Error(`Couldn't detect locale from file: ${localeFile}`);
|
|
13
|
+
}
|
|
14
|
+
const locale = match[1];
|
|
15
|
+
const translations = requireContext(localeFile).default;
|
|
16
|
+
this.locales[locale] = translations;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
getFallbacks() { return this.fallbacks; }
|
|
20
|
+
getLocale() { return this.locale; }
|
|
21
|
+
getLocales() { return this.locales; }
|
|
22
|
+
/**
|
|
23
|
+
* @param {string[]} fallbacks
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
26
|
+
setFallbacks(fallbacks) {
|
|
27
|
+
this.fallbacks = fallbacks;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} locale
|
|
31
|
+
* @returns {void}
|
|
32
|
+
*/
|
|
33
|
+
setLocale(locale) {
|
|
34
|
+
this.locale = locale;
|
|
35
|
+
events.emit("onLocaleChange", { locale });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const config = new Config();
|
|
39
|
+
export default config;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/events.js"],"names":[],"mappings":";AAIA,8CAAuC;yBAFd,QAAQ"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default class Po2Js {
|
|
2
|
+
/**
|
|
3
|
+
* @param {object} args
|
|
4
|
+
* @param {string} args.directory
|
|
5
|
+
*/
|
|
6
|
+
constructor({ directory }: {
|
|
7
|
+
directory: string;
|
|
8
|
+
});
|
|
9
|
+
directory: string;
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} file
|
|
13
|
+
* @param {string} ext
|
|
14
|
+
*/
|
|
15
|
+
readFile(file: string, ext: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=po2js.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"po2js.d.ts","sourceRoot":"","sources":["../../src/po2js.js"],"names":[],"mappings":"AAKA;IACE;;;OAGG;IACH,2BAFG;QAAqB,SAAS,EAAtB,MAAM;KAChB,EAGA;IADC,kBAA0B;IAG5B,qBAUC;IAED;;;OAGG;IACH,eAHW,MAAM,OACN,MAAM,iBAuBhB;CACF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export default class Po2Js {
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} args
|
|
7
|
+
* @param {string} args.directory
|
|
8
|
+
*/
|
|
9
|
+
constructor({ directory }) {
|
|
10
|
+
this.directory = directory;
|
|
11
|
+
}
|
|
12
|
+
async run() {
|
|
13
|
+
const files = await fs.readdir(this.directory);
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const ext = path.extname(file).toLowerCase();
|
|
16
|
+
if (ext == ".po") {
|
|
17
|
+
await this.readFile(file, ext);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} file
|
|
23
|
+
* @param {string} ext
|
|
24
|
+
*/
|
|
25
|
+
async readFile(file, ext) {
|
|
26
|
+
const fullPath = `${this.directory}/${file}`;
|
|
27
|
+
const baseName = path.basename(file, ext);
|
|
28
|
+
const jsFilePath = `${this.directory}/${baseName}.js`;
|
|
29
|
+
const fileContentBuffer = await fs.readFile(fullPath);
|
|
30
|
+
const fileContent = fileContentBuffer.toString();
|
|
31
|
+
const matches = fileContent.matchAll(/#: (.+?)\nmsgid \"(.+?)\"\nmsgstr \"(.+?)\"\n(\n|$)/g);
|
|
32
|
+
const translations = {};
|
|
33
|
+
for (const match of matches) {
|
|
34
|
+
const msgId = match[2];
|
|
35
|
+
const msgStr = match[3];
|
|
36
|
+
translations[msgId] = msgStr;
|
|
37
|
+
}
|
|
38
|
+
const jsCode = `export default ${JSON.stringify(translations, null, 2)}\n`;
|
|
39
|
+
await fs.writeFile(jsFilePath, jsCode);
|
|
40
|
+
console.log(`Wrote ${jsFilePath}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export default class Scanner {
|
|
2
|
+
/**
|
|
3
|
+
* @param {object} args
|
|
4
|
+
* @param {string} args.directory
|
|
5
|
+
* @param {string[]} args.extensions
|
|
6
|
+
* @param {string[]} args.files
|
|
7
|
+
* @param {string[]} args.ignores
|
|
8
|
+
* @param {string} args.output
|
|
9
|
+
*/
|
|
10
|
+
constructor({ directory, extensions, files, ignores, output, ...restArgs }: {
|
|
11
|
+
directory: string;
|
|
12
|
+
extensions: string[];
|
|
13
|
+
files: string[];
|
|
14
|
+
ignores: string[];
|
|
15
|
+
output: string;
|
|
16
|
+
});
|
|
17
|
+
directory: string;
|
|
18
|
+
extensions: string[];
|
|
19
|
+
files: string[];
|
|
20
|
+
ignores: string[];
|
|
21
|
+
output: string;
|
|
22
|
+
/** @type {Array<{fullPath: string, localPath: string}>} */
|
|
23
|
+
scannedFiles: Array<{
|
|
24
|
+
fullPath: string;
|
|
25
|
+
localPath: string;
|
|
26
|
+
}>;
|
|
27
|
+
/** @type {Record<string, {files: Array<{localPath: string, lineNumber: number}>}>} */
|
|
28
|
+
translations: Record<string, {
|
|
29
|
+
files: Array<{
|
|
30
|
+
localPath: string;
|
|
31
|
+
lineNumber: number;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
scan(): Promise<void>;
|
|
35
|
+
scanGivenFiles(): Promise<void>;
|
|
36
|
+
scanFiles(): Promise<void>;
|
|
37
|
+
scanFile({ localPath, fullPath }: {
|
|
38
|
+
localPath: any;
|
|
39
|
+
fullPath: any;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
scanDir(pathToScan: any, pathParts: any): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* @param {object} args
|
|
44
|
+
* @param {string} args.fullPath
|
|
45
|
+
* @param {string} args.localPath
|
|
46
|
+
*/
|
|
47
|
+
addScannedFile({ fullPath, localPath }: {
|
|
48
|
+
fullPath: string;
|
|
49
|
+
localPath: string;
|
|
50
|
+
}): void;
|
|
51
|
+
writeOutput(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/scanner.js"],"names":[],"mappings":"AAGA;IACE;;;;;;;OAOG;IACH,4EANG;QAAqB,SAAS,EAAtB,MAAM;QACS,UAAU,EAAzB,MAAM,EAAE;QACO,KAAK,EAApB,MAAM,EAAE;QACO,OAAO,EAAtB,MAAM,EAAE;QACK,MAAM,EAAnB,MAAM;KAChB,EAeA;IAXC,kBAA0B;IAC1B,qBAA4B;IAC5B,gBAAkB;IAClB,kBAAsB;IACtB,eAAoB;IAEpB,2DAA2D;IAC3D,cADW,KAAK,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,CAAC,CACjC;IAEtB,sFAAsF;IACtF,cADW,MAAM,CAAC,MAAM,EAAE;QAAC,KAAK,EAAE,KAAK,CAAC;YAAC,SAAS,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAC,CAAC,CAAA;KAAC,CAAC,CAC5D;IAGxB,sBAgBC;IAED,gCAOC;IAED,2BAQC;IAED;;;sBAyBC;IAED,wDA4BC;IAED;;;;OAIG;IACH,wCAHG;QAAqB,QAAQ,EAArB,MAAM;QACO,SAAS,EAAtB,MAAM;KAChB,QAMA;IAED,6BA4BC;CACF"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export default class Scanner {
|
|
4
|
+
/**
|
|
5
|
+
* @param {object} args
|
|
6
|
+
* @param {string} args.directory
|
|
7
|
+
* @param {string[]} args.extensions
|
|
8
|
+
* @param {string[]} args.files
|
|
9
|
+
* @param {string[]} args.ignores
|
|
10
|
+
* @param {string} args.output
|
|
11
|
+
*/
|
|
12
|
+
constructor({ directory, extensions, files, ignores, output, ...restArgs }) {
|
|
13
|
+
if (Object.keys(restArgs).length > 0)
|
|
14
|
+
throw new Error(`Unknown arrguments: ${Object.keys(restArgs).join(", ")}`);
|
|
15
|
+
this.directory = directory;
|
|
16
|
+
this.extensions = extensions;
|
|
17
|
+
this.files = files;
|
|
18
|
+
this.ignores = ignores;
|
|
19
|
+
this.output = output;
|
|
20
|
+
/** @type {Array<{fullPath: string, localPath: string}>} */
|
|
21
|
+
this.scannedFiles = [];
|
|
22
|
+
/** @type {Record<string, {files: Array<{localPath: string, lineNumber: number}>}>} */
|
|
23
|
+
this.translations = {};
|
|
24
|
+
}
|
|
25
|
+
async scan() {
|
|
26
|
+
if (this.directory) {
|
|
27
|
+
await this.scanDir(this.directory, []);
|
|
28
|
+
}
|
|
29
|
+
else if (this.files.length > 0) {
|
|
30
|
+
await this.scanGivenFiles();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw new Error("No directory or files given to scan");
|
|
34
|
+
}
|
|
35
|
+
await this.scanFiles();
|
|
36
|
+
if (this.output) {
|
|
37
|
+
await this.writeOutput();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.log({ translations: this.translations });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async scanGivenFiles() {
|
|
44
|
+
for (const file of this.files) {
|
|
45
|
+
this.scannedFiles.push({
|
|
46
|
+
fullPath: file,
|
|
47
|
+
localPath: file,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async scanFiles() {
|
|
52
|
+
const promises = [];
|
|
53
|
+
for (const fileData of this.scannedFiles) {
|
|
54
|
+
promises.push(this.scanFile(fileData));
|
|
55
|
+
}
|
|
56
|
+
await Promise.all(promises);
|
|
57
|
+
}
|
|
58
|
+
async scanFile({ localPath, fullPath }) {
|
|
59
|
+
if (!fullPath)
|
|
60
|
+
throw new Error(`Invalid fullPath given: ${fullPath}`);
|
|
61
|
+
const contentBuffer = await fs.readFile(fullPath);
|
|
62
|
+
const content = await contentBuffer.toString();
|
|
63
|
+
const lines = content.split(/(\r\n|\n)/);
|
|
64
|
+
for (let lineNumber = 1; lineNumber < lines.length; lineNumber++) {
|
|
65
|
+
const line = lines[lineNumber - 1];
|
|
66
|
+
const match = line.match(/_\(\s*("(.+?)"|'(.+?)')\s*(,|\))/);
|
|
67
|
+
if (match) {
|
|
68
|
+
const translationKey = match[2] || match[3];
|
|
69
|
+
if (!translationKey)
|
|
70
|
+
throw new Error(`Empty translation key from match: ${JSON.stringify(match)}`);
|
|
71
|
+
if (!(translationKey in this.translations)) {
|
|
72
|
+
this.translations[translationKey] = {
|
|
73
|
+
files: []
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
this.translations[translationKey].files.push({ localPath, lineNumber });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async scanDir(pathToScan, pathParts) {
|
|
81
|
+
const files = await fs.readdir(pathToScan);
|
|
82
|
+
const localPath = pathParts.join("/");
|
|
83
|
+
if (this.ignores.includes(localPath)) {
|
|
84
|
+
// console.log(`Ignoreing ${localPath}`)
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// console.log({files, path: pathParts.join("/")})
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const fullPath = `${pathToScan}/${file}`;
|
|
90
|
+
const stat = await fs.lstat(fullPath);
|
|
91
|
+
if (stat.isDirectory()) {
|
|
92
|
+
const newPathParts = pathParts.concat([file]);
|
|
93
|
+
this.scanDir(fullPath, newPathParts);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const ext = path.extname(file).toLowerCase();
|
|
97
|
+
if (this.extensions.includes(ext)) {
|
|
98
|
+
this.addScannedFile({ fullPath, localPath: `${localPath}/${file}` });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @param {object} args
|
|
105
|
+
* @param {string} args.fullPath
|
|
106
|
+
* @param {string} args.localPath
|
|
107
|
+
*/
|
|
108
|
+
addScannedFile({ fullPath, localPath }) {
|
|
109
|
+
this.scannedFiles.push({
|
|
110
|
+
fullPath,
|
|
111
|
+
localPath,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async writeOutput() {
|
|
115
|
+
const fp = await fs.open(this.output, "w");
|
|
116
|
+
let translationsCount = 0;
|
|
117
|
+
await fp.write("msgid \"\"\n");
|
|
118
|
+
await fp.write("msgstr \"\"\n");
|
|
119
|
+
await fp.write("\"Content-Type: text/plain; charset=UTF-8\\n\"\n");
|
|
120
|
+
await fp.write("\n"); // Empty line after header
|
|
121
|
+
for (const translationKey in this.translations) {
|
|
122
|
+
const translation = this.translations[translationKey];
|
|
123
|
+
if (translationsCount >= 1)
|
|
124
|
+
await fp.write("\n");
|
|
125
|
+
for (const file of translation.files) {
|
|
126
|
+
const { localPath, lineNumber } = file;
|
|
127
|
+
await fp.write(`#: ${localPath}:${lineNumber}\n`);
|
|
128
|
+
}
|
|
129
|
+
await fp.write(`msgid \"${translationKey}\"\n`);
|
|
130
|
+
await fp.write("msgstr \"\"\n");
|
|
131
|
+
translationsCount++;
|
|
132
|
+
}
|
|
133
|
+
await fp.close();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate-context.d.ts","sourceRoot":"","sources":["../../src/translate-context.js"],"names":[],"mappings":";AAEA,oCAAwC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} TranslateArgs
|
|
3
|
+
* @property {string} [defaultValue]
|
|
4
|
+
* @property {string[]} [locales]
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} msgId
|
|
8
|
+
* @param {Record<string, any>} [variables]
|
|
9
|
+
* @param {TranslateArgs | string[]} [argsCandidate] preferredLocales or arguments
|
|
10
|
+
*/
|
|
11
|
+
export default function translate(msgId: string, variables?: Record<string, any>, argsCandidate?: TranslateArgs | string[]): any;
|
|
12
|
+
export type TranslateArgs = {
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
locales?: string[];
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=translate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../src/translate.js"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH;;;;GAIG;AACH,yCAJW,MAAM,cACN,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,kBACnB,aAAa,GAAG,MAAM,EAAE,OA8ElC;;mBArFa,MAAM;cACN,MAAM,EAAE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import config from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {object} TranslateArgs
|
|
5
|
+
* @property {string} [defaultValue]
|
|
6
|
+
* @property {string[]} [locales]
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} msgId
|
|
10
|
+
* @param {Record<string, any>} [variables]
|
|
11
|
+
* @param {TranslateArgs | string[]} [argsCandidate] preferredLocales or arguments
|
|
12
|
+
*/
|
|
13
|
+
export default function translate(msgId, variables, argsCandidate) {
|
|
14
|
+
let args;
|
|
15
|
+
let preferredLocales;
|
|
16
|
+
if (Array.isArray(argsCandidate)) {
|
|
17
|
+
preferredLocales = argsCandidate;
|
|
18
|
+
}
|
|
19
|
+
else if (typeof args == "object") {
|
|
20
|
+
args = argsCandidate;
|
|
21
|
+
if (args.locales) {
|
|
22
|
+
preferredLocales = args.locales;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!preferredLocales) {
|
|
26
|
+
if (config.getLocale()) {
|
|
27
|
+
preferredLocales = [config.getLocale()];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.error("No 'preferredLocales' was given and a locale wasn't set in the configuration either");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
let translation;
|
|
34
|
+
if (preferredLocales) {
|
|
35
|
+
for (const preferredLocale of preferredLocales) {
|
|
36
|
+
const localeTranslations = config.getLocales()[preferredLocale];
|
|
37
|
+
if (!localeTranslations)
|
|
38
|
+
continue;
|
|
39
|
+
const localeTranslation = localeTranslations[msgId];
|
|
40
|
+
if (localeTranslation) {
|
|
41
|
+
translation = localeTranslation;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!translation) {
|
|
47
|
+
const fallbacks = config.getFallbacks();
|
|
48
|
+
if (fallbacks) {
|
|
49
|
+
for (const fallback of config.getFallbacks()) {
|
|
50
|
+
const localeTranslations = config.getLocales()[fallback];
|
|
51
|
+
if (!localeTranslations)
|
|
52
|
+
continue;
|
|
53
|
+
const localeTranslation = localeTranslations[msgId];
|
|
54
|
+
if (localeTranslation) {
|
|
55
|
+
translation = localeTranslation;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!translation) {
|
|
62
|
+
if (args?.defaultValue) {
|
|
63
|
+
translation = args.defaultValue;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
translation = msgId;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (variables) {
|
|
70
|
+
for (const key in variables) {
|
|
71
|
+
const value = variables[key];
|
|
72
|
+
const replaceKey = `%{${key}}`;
|
|
73
|
+
translation = translation.replaceAll(replaceKey, value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return translation;
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-translate-expo.d.ts","sourceRoot":"","sources":["../../src/use-translate-expo.js"],"names":[],"mappings":";AAKA,yCA2BC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import translate from "./translate.js";
|
|
2
|
+
import TranslateContext from "./translate-context.js";
|
|
3
|
+
import { useCallback, useContext, useMemo } from "react";
|
|
4
|
+
import { useLocales } from "expo-localization";
|
|
5
|
+
const useTranslateExpo = () => {
|
|
6
|
+
const localeContext = useContext(TranslateContext);
|
|
7
|
+
const locales = useLocales();
|
|
8
|
+
const preferredLocales = useMemo(() => {
|
|
9
|
+
let preferredLocales = [];
|
|
10
|
+
if (localeContext?.locale) {
|
|
11
|
+
preferredLocales.push(localeContext.locale);
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(locales)) {
|
|
14
|
+
for (const localeData of locales) {
|
|
15
|
+
preferredLocales.push(localeData.languageCode);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return preferredLocales;
|
|
19
|
+
}, [localeContext?.locale, locales]);
|
|
20
|
+
const currentTranslation = useCallback((msgId, variables, args = {}) => {
|
|
21
|
+
args.locales ||= preferredLocales;
|
|
22
|
+
return translate(msgId, variables, args);
|
|
23
|
+
}, [preferredLocales]);
|
|
24
|
+
return currentTranslation;
|
|
25
|
+
};
|
|
26
|
+
export default useTranslateExpo;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-translate.d.ts","sourceRoot":"","sources":["../../src/with-translate.jsx"],"names":[],"mappings":";AAOA;;;QAsCC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import config from "./config.js";
|
|
3
|
+
import events from "./events.js";
|
|
4
|
+
import { useCallback, useMemo, useState } from "react";
|
|
5
|
+
import TranslateContext from "./translate-context.js";
|
|
6
|
+
import useEventEmitter from "@kaspernj/api-maker/build/use-event-emitter.js";
|
|
7
|
+
import { useLocales } from "expo-localization";
|
|
8
|
+
const WithTranslate = ({ children, ...restProps }) => {
|
|
9
|
+
const locales = useLocales();
|
|
10
|
+
const [locale, setLocale] = useState(config.getLocale());
|
|
11
|
+
const actualLocales = useMemo(() => {
|
|
12
|
+
const actualLocales = [];
|
|
13
|
+
if (locale) {
|
|
14
|
+
actualLocales.push(locale);
|
|
15
|
+
}
|
|
16
|
+
for (const locale of locales) {
|
|
17
|
+
actualLocales.push(locale.languageCode);
|
|
18
|
+
}
|
|
19
|
+
return actualLocales;
|
|
20
|
+
}, [locale, locales]);
|
|
21
|
+
const contextData = useMemo(() => ({ locale, locales: actualLocales }), [actualLocales]);
|
|
22
|
+
const onChangeLocale = useCallback(({ locale }) => {
|
|
23
|
+
setLocale(locale);
|
|
24
|
+
}, []);
|
|
25
|
+
// @ts-expect-error
|
|
26
|
+
useEventEmitter(events, "onLocaleChange", onChangeLocale);
|
|
27
|
+
const restPropsKeys = Object.keys(restProps);
|
|
28
|
+
if (restPropsKeys.length > 0) {
|
|
29
|
+
throw new Error(`Unknown props given: ${restPropsKeys.join(", ")}`);
|
|
30
|
+
}
|
|
31
|
+
return (_jsx(TranslateContext.Provider, { value: contextData, children: children }));
|
|
32
|
+
};
|
|
33
|
+
export default WithTranslate;
|
package/package.json
CHANGED
|
@@ -4,12 +4,19 @@
|
|
|
4
4
|
},
|
|
5
5
|
"name": "gettext-universal",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "1.0.
|
|
8
|
-
"main": "index.js",
|
|
7
|
+
"version": "1.0.14",
|
|
8
|
+
"main": "build/index.js",
|
|
9
|
+
"types": "build/index.d.ts",
|
|
10
|
+
"files": ["build/**"],
|
|
9
11
|
"scripts": {
|
|
12
|
+
"build": "rm -rf build && tsc && npm run chmod-executables",
|
|
13
|
+
"chmod-executables": "chmod +x build/bin/gettext-universal.js && chmod +x build/bin/po2js.js",
|
|
10
14
|
"gettext-universal": "node bin/gettext-universal.js",
|
|
11
15
|
"po2js": "node bin/po2js.js",
|
|
12
|
-
"
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"test": "jasmine",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"watch": "tsc -w"
|
|
13
20
|
},
|
|
14
21
|
"repository": {
|
|
15
22
|
"type": "git",
|
|
@@ -31,6 +38,12 @@
|
|
|
31
38
|
"diggerize": "^1.0.5"
|
|
32
39
|
},
|
|
33
40
|
"devDependencies": {
|
|
34
|
-
"jasmine": "^5.7.1"
|
|
41
|
+
"jasmine": "^5.7.1",
|
|
42
|
+
"typescript": "^5.9.3"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@kaspernj/api-maker": "^1.0.2055",
|
|
46
|
+
"expo-localization": "^17.0.8",
|
|
47
|
+
"react": "^19.2.3"
|
|
35
48
|
}
|
|
36
49
|
}
|
package/.github/dependabot.yml
DELETED
package/bin/po2js.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import Po2Js from "../src/po2js.js"
|
|
4
|
-
|
|
5
|
-
const processArgs = process.argv.slice(2)
|
|
6
|
-
let directory
|
|
7
|
-
|
|
8
|
-
for (let i = 0; i < processArgs.length; i++) {
|
|
9
|
-
const arg = processArgs[i]
|
|
10
|
-
|
|
11
|
-
if (arg == "--directory") {
|
|
12
|
-
directory = processArgs[++i]
|
|
13
|
-
} else {
|
|
14
|
-
throw new Error(`Unknown argument: ${arg}`)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const po2Js = new Po2Js({directory})
|
|
19
|
-
|
|
20
|
-
await po2Js.run()
|
package/peak_flow.yml
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import config from "../src/config.js"
|
|
2
|
-
import translate from "../src/translate.js"
|
|
3
|
-
|
|
4
|
-
describe("gettext-universal", () => {
|
|
5
|
-
it("falls back to the given msgId", () => {
|
|
6
|
-
config.setLocale("en")
|
|
7
|
-
config.locales = {
|
|
8
|
-
en: {}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const result = translate("Hello world")
|
|
12
|
-
|
|
13
|
-
expect(result).toEqual("Hello world")
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it("replaces placeholders with variables", () => {
|
|
17
|
-
config.setLocale("en")
|
|
18
|
-
config.locales = {
|
|
19
|
-
"en": {
|
|
20
|
-
"Hello name": "Hello %{name}"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const result = translate("Hello name", {name: "Kasper"})
|
|
25
|
-
|
|
26
|
-
expect(result).toEqual("Hello Kasper")
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it("replaces placeholders with variables in defaults", () => {
|
|
30
|
-
config.setLocale(null)
|
|
31
|
-
config.locales = {
|
|
32
|
-
"en": {
|
|
33
|
-
"Hello world": "Hello world"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const result = translate("Hello %{name}", {name: "Kasper"})
|
|
38
|
-
|
|
39
|
-
expect(result).toEqual("Hello Kasper")
|
|
40
|
-
})
|
|
41
|
-
})
|
package/spec/support/jasmine.js
DELETED
package/src/config.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import events from "./events.js"
|
|
2
|
-
|
|
3
|
-
class Config {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.locales = {}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
loadTranslationsFromRequireContext(requireContext) {
|
|
9
|
-
for (const localeFile of requireContext.keys()) {
|
|
10
|
-
const match = localeFile.match(/([a-z]{2}).js$/)
|
|
11
|
-
|
|
12
|
-
if (!match) {
|
|
13
|
-
throw new Error(`Couldn't detect locale from file: ${localeFile}`)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const locale = match[1]
|
|
17
|
-
const translations = requireContext(localeFile).default
|
|
18
|
-
|
|
19
|
-
this.locales[locale] = translations
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
getFallbacks = () => this.fallbacks
|
|
24
|
-
getLocale = () => this.locale
|
|
25
|
-
getLocales = () => this.locales
|
|
26
|
-
|
|
27
|
-
setFallbacks(fallbacks) {
|
|
28
|
-
this.fallbacks = fallbacks
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
setLocale(locale) {
|
|
32
|
-
this.locale = locale
|
|
33
|
-
events.emit("onLocaleChange", {locale})
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const config = new Config()
|
|
38
|
-
|
|
39
|
-
export default config
|
package/src/events.js
DELETED
package/src/po2js.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {promises as fs} from "fs"
|
|
2
|
-
import path from "path"
|
|
3
|
-
|
|
4
|
-
export default class Po2Js {
|
|
5
|
-
constructor({directory}) {
|
|
6
|
-
this.directory = directory
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async run() {
|
|
10
|
-
const files = await fs.readdir(this.directory)
|
|
11
|
-
|
|
12
|
-
for (const file of files) {
|
|
13
|
-
const ext = path.extname(file).toLowerCase()
|
|
14
|
-
|
|
15
|
-
if (ext == ".po") {
|
|
16
|
-
await this.readFile(file, ext)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async readFile(file, ext) {
|
|
22
|
-
const fullPath = `${this.directory}/${file}`
|
|
23
|
-
const baseName = path.basename(file, ext)
|
|
24
|
-
const jsFilePath = `${this.directory}/${baseName}.js`
|
|
25
|
-
const fileContentBuffer = await fs.readFile(fullPath)
|
|
26
|
-
const fileContent = fileContentBuffer.toString()
|
|
27
|
-
const matches = fileContent.matchAll(/#: (.+?)\nmsgid \"(.+?)\"\nmsgstr \"(.+?)\"\n(\n|$)/g)
|
|
28
|
-
const translations = {}
|
|
29
|
-
|
|
30
|
-
for (const match of matches) {
|
|
31
|
-
const msgId = match[2]
|
|
32
|
-
const msgStr = match[3]
|
|
33
|
-
|
|
34
|
-
translations[msgId] = msgStr
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const jsCode = `export default ${JSON.stringify(translations, null, 2)}\n`
|
|
38
|
-
|
|
39
|
-
await fs.writeFile(jsFilePath, jsCode)
|
|
40
|
-
|
|
41
|
-
console.log(`Wrote ${jsFilePath}`)
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/scanner.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import {promises as fs} from "fs"
|
|
2
|
-
import path from "path"
|
|
3
|
-
|
|
4
|
-
export default class Scanner {
|
|
5
|
-
constructor({directory, extensions, files, ignores, output, ...restArgs}) {
|
|
6
|
-
if (Object.keys(restArgs).length > 0) throw new Error(`Unknown arrguments: ${Object.keys(restArgs).join(", ")}`)
|
|
7
|
-
|
|
8
|
-
this.directory = directory
|
|
9
|
-
this.extensions = extensions
|
|
10
|
-
this.files = files
|
|
11
|
-
this.ignores = ignores
|
|
12
|
-
this.output = output
|
|
13
|
-
this.scannedFiles = []
|
|
14
|
-
this.translations = {}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async scan() {
|
|
18
|
-
if (this.directory) {
|
|
19
|
-
await this.scanDir(this.directory, [])
|
|
20
|
-
} else if (this.files.length > 0) {
|
|
21
|
-
await this.scanGivenFiles()
|
|
22
|
-
} else {
|
|
23
|
-
throw new Error("No directory or files given to scan")
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
await this.scanFiles()
|
|
27
|
-
|
|
28
|
-
if (this.output) {
|
|
29
|
-
await this.writeOutput()
|
|
30
|
-
} else {
|
|
31
|
-
console.log({translations: this.translations})
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async scanGivenFiles() {
|
|
36
|
-
for (const file of this.files) {
|
|
37
|
-
this.scannedFiles.push({
|
|
38
|
-
fullPath: file,
|
|
39
|
-
localPath: file,
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async scanFiles() {
|
|
45
|
-
const promises = []
|
|
46
|
-
|
|
47
|
-
for (const fileData of this.scannedFiles) {
|
|
48
|
-
promises.push(this.scanFile(fileData))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
await Promise.all(promises)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async scanFile({localPath, fullPath}) {
|
|
55
|
-
if (!fullPath) throw new Error(`Invalid fullPath given: ${fullPath}`)
|
|
56
|
-
|
|
57
|
-
const contentBuffer = await fs.readFile(fullPath)
|
|
58
|
-
const content = await contentBuffer.toString()
|
|
59
|
-
const lines = content.split(/(\r\n|\n)/)
|
|
60
|
-
|
|
61
|
-
for (let lineNumber = 1; lineNumber < lines.length; lineNumber++) {
|
|
62
|
-
const line = lines[lineNumber - 1]
|
|
63
|
-
const match = line.match(/_\(\s*("(.+?)"|'(.+?)')\s*(,|\))/)
|
|
64
|
-
|
|
65
|
-
if (match) {
|
|
66
|
-
const translationKey = match[2] || match[3]
|
|
67
|
-
|
|
68
|
-
if (!translationKey) throw new Error("Empty translation key from match", {match})
|
|
69
|
-
|
|
70
|
-
if (!(translationKey in this.translations)) {
|
|
71
|
-
this.translations[translationKey] = {
|
|
72
|
-
files: []
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
this.translations[translationKey].files.push({localPath, lineNumber})
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async scanDir(pathToScan, pathParts) {
|
|
82
|
-
const files = await fs.readdir(pathToScan)
|
|
83
|
-
const localPath = pathParts.join("/")
|
|
84
|
-
|
|
85
|
-
if (this.ignores.includes(localPath)) {
|
|
86
|
-
// console.log(`Ignoreing ${localPath}`)
|
|
87
|
-
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// console.log({files, path: pathParts.join("/")})
|
|
92
|
-
|
|
93
|
-
for (const file of files) {
|
|
94
|
-
const fullPath = `${pathToScan}/${file}`
|
|
95
|
-
const stat = await fs.lstat(fullPath)
|
|
96
|
-
|
|
97
|
-
if (stat.isDirectory()) {
|
|
98
|
-
const newPathParts = pathParts.concat([file])
|
|
99
|
-
|
|
100
|
-
this.scanDir(fullPath, newPathParts)
|
|
101
|
-
} else {
|
|
102
|
-
const ext = path.extname(file).toLowerCase()
|
|
103
|
-
|
|
104
|
-
if (this.extensions.includes(ext)) {
|
|
105
|
-
this.addScannedFile({fullPath, localPath: `${localPath}/${file}`})
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
addScannedFile({fullPath, localPath}) {
|
|
112
|
-
this.scannedFiles.push({
|
|
113
|
-
fullPath,
|
|
114
|
-
localPath,
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async writeOutput() {
|
|
119
|
-
const fp = await fs.open(this.output, "w")
|
|
120
|
-
|
|
121
|
-
let translationsCount = 0
|
|
122
|
-
|
|
123
|
-
await fp.write("msgid \"\"\n")
|
|
124
|
-
await fp.write("msgstr \"\"\n")
|
|
125
|
-
await fp.write("\"Content-Type: text/plain; charset=UTF-8\\n\"\n")
|
|
126
|
-
await fp.write("\n") // Empty line after header
|
|
127
|
-
|
|
128
|
-
for (const translationKey in this.translations) {
|
|
129
|
-
const translation = this.translations[translationKey]
|
|
130
|
-
|
|
131
|
-
if (translationsCount >= 1) await fp.write("\n")
|
|
132
|
-
|
|
133
|
-
for (const file of translation.files) {
|
|
134
|
-
const {localPath, lineNumber} = file
|
|
135
|
-
|
|
136
|
-
await fp.write(`#: ${localPath}:${lineNumber}\n`)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
await fp.write(`msgid \"${translationKey}\"\n`)
|
|
140
|
-
await fp.write("msgstr \"\"\n")
|
|
141
|
-
|
|
142
|
-
translationsCount++
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
await fp.close()
|
|
146
|
-
}
|
|
147
|
-
}
|
package/src/translate.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import config from "./config.js"
|
|
2
|
-
|
|
3
|
-
const translate = (msgId, variables, args) => {
|
|
4
|
-
let preferredLocales
|
|
5
|
-
|
|
6
|
-
if (Array.isArray(args)) {
|
|
7
|
-
preferredLocales = args
|
|
8
|
-
args = null
|
|
9
|
-
} else if (args?.locales) {
|
|
10
|
-
preferredLocales = args.locales
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (!preferredLocales) {
|
|
14
|
-
if (config.getLocale()) {
|
|
15
|
-
preferredLocales = [config.getLocale()]
|
|
16
|
-
} else {
|
|
17
|
-
console.error("No 'preferredLocales' was given and a locale wasn't set in the configuration either")
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let translation
|
|
22
|
-
|
|
23
|
-
if (preferredLocales) {
|
|
24
|
-
for (const preferredLocale of preferredLocales) {
|
|
25
|
-
const localeTranslations = config.getLocales()[preferredLocale]
|
|
26
|
-
|
|
27
|
-
if (!localeTranslations) continue
|
|
28
|
-
|
|
29
|
-
const localeTranslation = localeTranslations[msgId]
|
|
30
|
-
|
|
31
|
-
if (localeTranslation) {
|
|
32
|
-
translation = localeTranslation
|
|
33
|
-
break
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!translation) {
|
|
39
|
-
const fallbacks = config.getFallbacks()
|
|
40
|
-
|
|
41
|
-
if (fallbacks) {
|
|
42
|
-
for (const fallback of config.getFallbacks()) {
|
|
43
|
-
const localeTranslations = config.getLocales()[fallback]
|
|
44
|
-
|
|
45
|
-
if (!localeTranslations) continue
|
|
46
|
-
|
|
47
|
-
const localeTranslation = localeTranslations[msgId]
|
|
48
|
-
|
|
49
|
-
if (localeTranslation) {
|
|
50
|
-
translation = localeTranslation
|
|
51
|
-
break
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!translation) {
|
|
58
|
-
if (args?.defaultValue) {
|
|
59
|
-
translation = args.defaultValue
|
|
60
|
-
} else {
|
|
61
|
-
translation = msgId
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (variables) {
|
|
66
|
-
for (const key in variables) {
|
|
67
|
-
const value = variables[key]
|
|
68
|
-
const replaceKey = `%{${key}}`
|
|
69
|
-
|
|
70
|
-
translation = translation.replaceAll(replaceKey, value)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return translation
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export default translate
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import config from "./config.js"
|
|
2
|
-
import events from "./events.js"
|
|
3
|
-
import translate from "./translate.js"
|
|
4
|
-
import {createContext, useCallback, useContext, useMemo, useState} from "react"
|
|
5
|
-
import useEventEmitter from "@kaspernj/api-maker/build/use-event-emitter"
|
|
6
|
-
import {useLocales} from "expo-localization"
|
|
7
|
-
|
|
8
|
-
const TranslateContext = createContext()
|
|
9
|
-
|
|
10
|
-
const WithTranslate = ({children, ...restProps}) => {
|
|
11
|
-
const locales = useLocales()
|
|
12
|
-
const [locale, setLocale] = useState(config.getLocale())
|
|
13
|
-
|
|
14
|
-
const actualLocales = useMemo(() => {
|
|
15
|
-
const actualLocales = []
|
|
16
|
-
|
|
17
|
-
if (locale) {
|
|
18
|
-
actualLocales.push(locale)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
for (const locale of locales) {
|
|
22
|
-
actualLocales.push(locale.languageCode)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return actualLocales
|
|
26
|
-
}, [locale, locales])
|
|
27
|
-
|
|
28
|
-
const contextData = useMemo(() => ({locale, locales: actualLocales}), [actualLocales])
|
|
29
|
-
|
|
30
|
-
const onChangeLocale = useCallback(({locale}) => {
|
|
31
|
-
setLocale(locale)
|
|
32
|
-
}, [])
|
|
33
|
-
|
|
34
|
-
useEventEmitter(events, "onLocaleChange", onChangeLocale)
|
|
35
|
-
|
|
36
|
-
const restPropsKeys = Object.keys(restProps)
|
|
37
|
-
|
|
38
|
-
if (restPropsKeys.length > 0) {
|
|
39
|
-
throw new Error(`Unknown props given: ${restPropsKeys.join(", ")}`)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<TranslateContext.Provider value={contextData}>
|
|
44
|
-
{children}
|
|
45
|
-
</TranslateContext.Provider>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const useTranslateExpo = () => {
|
|
50
|
-
const localeContext = useContext(TranslateContext)
|
|
51
|
-
const locales = useLocales()
|
|
52
|
-
|
|
53
|
-
const preferredLocales = useMemo(() => {
|
|
54
|
-
let preferredLocales = []
|
|
55
|
-
|
|
56
|
-
if (localeContext?.locale) {
|
|
57
|
-
preferredLocales.push(localeContext.locale)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (Array.isArray(locales)) {
|
|
61
|
-
for (const localeData of locales) {
|
|
62
|
-
preferredLocales.push(localeData.languageCode)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return preferredLocales
|
|
67
|
-
}, [localeContext?.locale, locales])
|
|
68
|
-
|
|
69
|
-
const currentTranslation = useCallback((msgId, variables, args = {}) => {
|
|
70
|
-
args.locales ||= preferredLocales
|
|
71
|
-
|
|
72
|
-
return translate(msgId, variables, args)
|
|
73
|
-
}, [preferredLocales])
|
|
74
|
-
|
|
75
|
-
return currentTranslation
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export {WithTranslate}
|
|
79
|
-
export default useTranslateExpo
|