glotstack 0.0.4 → 0.0.7
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/dist/cli.js +204 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.js +71 -22
- package/dist/index.js.map +1 -1
- package/eslint-raw-string.mjs +15 -0
- package/package.json +16 -9
- package/src/cli.tsx +197 -41
- package/src/index.tsx +78 -35
- package/tsconfig.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
3
26
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
27
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
28
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -20,12 +43,140 @@ const findConfig_1 = require("./util/findConfig");
|
|
|
20
43
|
const process_1 = require("process");
|
|
21
44
|
const object_1 = require("./util/object");
|
|
22
45
|
const yaml_1 = require("./util/yaml");
|
|
46
|
+
const eslint_1 = __importDefault(require("eslint"));
|
|
47
|
+
const readline = __importStar(require("node:readline/promises"));
|
|
48
|
+
const node_process_1 = require("node:process");
|
|
49
|
+
const undici_1 = require("undici");
|
|
50
|
+
const undici_2 = require("undici");
|
|
51
|
+
const node_fs_1 = require("node:fs");
|
|
52
|
+
const promises_1 = require("node:fs/promises");
|
|
53
|
+
const node_path_1 = require("node:path");
|
|
54
|
+
const fetchGlotstack = function (url, apiKey, body, overrideHeaders) {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
console.info(`Extracting translations with: ${url}`);
|
|
57
|
+
const headers = Object.assign({ 'authorization': `Bearer ${apiKey}` }, (overrideHeaders == null ? {} : overrideHeaders));
|
|
58
|
+
let payloadBody;
|
|
59
|
+
if (!(body instanceof undici_2.FormData)) {
|
|
60
|
+
headers['content-type'] = 'application/json';
|
|
61
|
+
payloadBody = JSON.stringify(body);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
payloadBody = body;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const res = yield (0, undici_1.fetch)(url, { method: 'POST', body: payloadBody, headers });
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
70
|
+
return res.json();
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.error('Fetch failed:', err);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
function unflatten(flat) {
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const flatKey in flat) {
|
|
81
|
+
const parts = flatKey.split('.');
|
|
82
|
+
let current = result;
|
|
83
|
+
parts.forEach((part, idx) => {
|
|
84
|
+
if (idx === parts.length - 1) {
|
|
85
|
+
current[part] = flat[flatKey];
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (!(part in current)) {
|
|
89
|
+
current[part] = {};
|
|
90
|
+
}
|
|
91
|
+
current = current[part];
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
function resolveConfigAndOptions(options) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const configPath = (0, findConfig_1.findGlotstackConfig)((0, process_1.cwd)());
|
|
100
|
+
let config = {};
|
|
101
|
+
if (configPath != null) {
|
|
102
|
+
console.info('Loading config file at ', configPath);
|
|
103
|
+
try {
|
|
104
|
+
const text = yield fs_1.promises.readFile(configPath, 'utf-8');
|
|
105
|
+
config = JSON.parse(text);
|
|
106
|
+
console.info('Loaded config file', config);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
//pass
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if ('outputLocales' in options) {
|
|
113
|
+
if (options.outputLocales.includes('en-US')) {
|
|
114
|
+
console.warn('en-US detected in outputLocales, removing');
|
|
115
|
+
options.outputLocales = options.outputLocales.filter((x) => x !== 'en-US');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return (0, object_1.merge)(config, options);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
23
121
|
function run(args) {
|
|
24
122
|
return __awaiter(this, void 0, void 0, function* () {
|
|
123
|
+
var _a, _b;
|
|
124
|
+
commander_1.program
|
|
125
|
+
.command('extract-translations')
|
|
126
|
+
.description('extract translations from all compatible source files.')
|
|
127
|
+
.option('--source-path [path]', 'to source files root directory', '.')
|
|
128
|
+
.option('--api-origin [url]', 'glotstack api origin', (_a = process.env.GLOTSTACK_HOST) !== null && _a !== void 0 ? _a : 'https://glotstack.ai')
|
|
129
|
+
.option('--api-key [key]', 'api key for glotstack.ai')
|
|
130
|
+
.option('--yes', 'skip confirm checks', false)
|
|
131
|
+
.action((options) => __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
if (!options.apiOrigin) {
|
|
133
|
+
throw new Error('apiOrigin must be specified');
|
|
134
|
+
}
|
|
135
|
+
const linter = new eslint_1.default.ESLint({ overrideConfigFile: path_1.default.join(__dirname, '..', 'eslint-raw-string.mjs') });
|
|
136
|
+
const results = yield linter.lintFiles(["./**/*"]);
|
|
137
|
+
const filesWithIssues = results
|
|
138
|
+
.filter((r) => r.errorCount + r.warningCount > 0)
|
|
139
|
+
.map((r) => r.filePath);
|
|
140
|
+
const rl = readline.createInterface({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
141
|
+
const askToSend = () => __awaiter(this, void 0, void 0, function* () {
|
|
142
|
+
if (options.yes) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
const response = yield rl.question(`Your source are going to be sent to our LLM -- they should not contain any secrets. Proceed? (yes/no):`);
|
|
146
|
+
if (response === 'yes') {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
else if (response !== 'no') {
|
|
150
|
+
console.error('Please respond with yes or no.');
|
|
151
|
+
return askToSend();
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
const send = yield askToSend();
|
|
158
|
+
if (send) {
|
|
159
|
+
console.info('Sending files to LLM');
|
|
160
|
+
const url = `${options.apiOrigin}/uploads/translations/extract`;
|
|
161
|
+
const form = new undici_2.FormData();
|
|
162
|
+
for (let i = 0; i < filesWithIssues.length; i++) {
|
|
163
|
+
const filePath = filesWithIssues[i];
|
|
164
|
+
form.append(`file_${i}`, yield (0, node_fs_1.openAsBlob)(filePath), filePath);
|
|
165
|
+
console.debug(`Uploading file: ${filePath}`);
|
|
166
|
+
}
|
|
167
|
+
const data = yield fetchGlotstack(url, options.apiKey, form);
|
|
168
|
+
data.translations.map(elem => console.info(`${elem.name}:\n ${elem.modified_source.url}\n\n`));
|
|
169
|
+
rl.close();
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
rl.close();
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
25
175
|
commander_1.program
|
|
26
176
|
.command('get-translations')
|
|
177
|
+
.description('fetch translations for all [output-locals...]. Use .glotstack.json for repeatable results.')
|
|
27
178
|
.option('--source-path [path]', 'path to en-US.json (or your canonical source json)')
|
|
28
|
-
.option('--api-origin [url]', 'glotstack api origin', '
|
|
179
|
+
.option('--api-origin [url]', 'glotstack api origin', (_b = process.env.GLOTSTACK_HOST) !== null && _b !== void 0 ? _b : 'https://glotstack.ai')
|
|
29
180
|
.option('--output-dir [path]', 'path to output directory')
|
|
30
181
|
.option('--api-key [key]', 'api key for glotstack.ai')
|
|
31
182
|
.option('--project-id [id]', '(optional) specific project to use')
|
|
@@ -44,24 +195,17 @@ function run(args) {
|
|
|
44
195
|
//pass
|
|
45
196
|
}
|
|
46
197
|
}
|
|
47
|
-
const resolved = (
|
|
48
|
-
|
|
49
|
-
if (resolved.outputLocales.includes('en-US')) {
|
|
50
|
-
console.warn('en-US detected in outputLocales, removing');
|
|
51
|
-
resolved.outputLocales = resolved.outputLocales.filter((x) => x !== 'en-US');
|
|
52
|
-
}
|
|
53
|
-
if (!sourcePath) {
|
|
198
|
+
const resolved = yield resolveConfigAndOptions(Object.assign(Object.assign({}, options), { outputLocales: outputLocales }));
|
|
199
|
+
if (!resolved.sourcePath) {
|
|
54
200
|
throw new Error('sourcePath must be specified');
|
|
55
201
|
}
|
|
56
|
-
if (!apiOrigin) {
|
|
202
|
+
if (!resolved.apiOrigin) {
|
|
57
203
|
throw new Error('apiOrigin must be specified');
|
|
58
204
|
}
|
|
59
|
-
if (!outputDir) {
|
|
205
|
+
if (!resolved.outputDir) {
|
|
60
206
|
throw new Error('outputDir must be specified');
|
|
61
207
|
}
|
|
62
|
-
const
|
|
63
|
-
console.info(`Fetching translations from: ${url}`);
|
|
64
|
-
const absPath = path_1.default.resolve(sourcePath);
|
|
208
|
+
const absPath = path_1.default.resolve(resolved.sourcePath);
|
|
65
209
|
const fileContent = yield fs_1.promises.readFile(absPath, 'utf-8');
|
|
66
210
|
let json = null;
|
|
67
211
|
try {
|
|
@@ -76,28 +220,55 @@ function run(args) {
|
|
|
76
220
|
throw err;
|
|
77
221
|
}
|
|
78
222
|
}
|
|
79
|
-
const body = Object.assign({ locales: resolved.outputLocales, translations: json }, Object.assign({}, (projectId != null ? { projectId } : {})));
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
223
|
+
const body = Object.assign({ locales: resolved.outputLocales, translations: json }, Object.assign({}, (resolved.projectId != null ? { projectId: resolved.projectId } : {})));
|
|
224
|
+
const url = `${options.apiOrigin}/api/translations`;
|
|
225
|
+
const data = yield fetchGlotstack(url, resolved.apiKey, body);
|
|
226
|
+
console.info('Received translations:', data);
|
|
227
|
+
Object.entries(data.data).map(([key, val]) => {
|
|
228
|
+
const p = `${resolved.outputDir}/${key}.json`;
|
|
229
|
+
console.info(`Writing file ${p}`);
|
|
230
|
+
fs_1.promises.writeFile(`${resolved.outputDir}/${key}.json`, JSON.stringify(val, null, 2));
|
|
231
|
+
});
|
|
232
|
+
}));
|
|
233
|
+
commander_1.program
|
|
234
|
+
.command('format-json')
|
|
235
|
+
.description('format files in --source-path [path] to nested (not flat)')
|
|
236
|
+
.option('--source-path [path]', 'to source files root directory', '.')
|
|
237
|
+
.option('--yes', 'skip confirm checks', false)
|
|
238
|
+
.action((options) => __awaiter(this, void 0, void 0, function* () {
|
|
239
|
+
if (!options.sourcePath) {
|
|
240
|
+
throw new Error('sourcePath must be specified');
|
|
96
241
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
242
|
+
const rl = readline.createInterface({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
243
|
+
const askToSend = () => __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
if (options.yes) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
const response = yield rl.question(`This will update your source files -- have you checked them into SCM/git? Type yes to proceed (yes/no):`);
|
|
248
|
+
if (response === 'yes') {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
else if (response !== 'no') {
|
|
252
|
+
console.error('Please respond with yes or no.');
|
|
253
|
+
return askToSend();
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const yes = yield askToSend();
|
|
260
|
+
if (yes) {
|
|
261
|
+
const files = yield (0, promises_1.readdir)(options.sourcePath);
|
|
262
|
+
for (let i = 0; i < (yield files).length; i++) {
|
|
263
|
+
const fp = (0, node_path_1.resolve)(options.sourcePath, files[i]);
|
|
264
|
+
const text = yield (0, promises_1.readFile)(fp, 'utf-8');
|
|
265
|
+
const json = JSON.parse(text);
|
|
266
|
+
const formatted = JSON.stringify(unflatten(json), null, 2);
|
|
267
|
+
yield (0, promises_1.writeFile)(fp, formatted);
|
|
268
|
+
}
|
|
269
|
+
rl.close();
|
|
100
270
|
}
|
|
271
|
+
rl.close();
|
|
101
272
|
}));
|
|
102
273
|
yield commander_1.program.parseAsync(args);
|
|
103
274
|
});
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
export type LocaleRegion =
|
|
2
|
+
export type LocaleRegion = string;
|
|
3
3
|
export interface TranslationLeaf {
|
|
4
4
|
value: string;
|
|
5
5
|
context?: string;
|
|
@@ -16,18 +16,28 @@ export interface ContextType {
|
|
|
16
16
|
t: (key: string, options?: {
|
|
17
17
|
locale?: LocaleRegion;
|
|
18
18
|
}) => string;
|
|
19
|
+
ssr: boolean;
|
|
19
20
|
}
|
|
20
21
|
export declare const GlotstackContext: React.Context<ContextType>;
|
|
21
22
|
interface GlotstackProviderProps {
|
|
22
23
|
children: React.ReactNode;
|
|
23
|
-
initialTranslations?: Translations
|
|
24
|
+
initialTranslations?: Record<string, Translations>;
|
|
24
25
|
initialLocale?: LocaleRegion;
|
|
25
26
|
onTranslationLoaded?: (locale: LocaleRegion, translations: Translations) => void;
|
|
26
27
|
onLocaleChange?: (locale: LocaleRegion) => void;
|
|
27
|
-
importMethod:
|
|
28
|
+
importMethod: ContextType['importMethod'];
|
|
29
|
+
ssr?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare enum LogLevel {
|
|
32
|
+
DEBUG = 0,
|
|
33
|
+
LOG = 1,
|
|
34
|
+
INFO = 2,
|
|
35
|
+
WARNING = 3,
|
|
36
|
+
ERROR = 4
|
|
28
37
|
}
|
|
38
|
+
export declare const setLogLevel: (level: LogLevel) => void;
|
|
29
39
|
export declare const access: (key: string, locale: LocaleRegion, translations: Translations) => string;
|
|
30
|
-
export declare const GlotstackProvider: ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod }: GlotstackProviderProps) =>
|
|
40
|
+
export declare const GlotstackProvider: ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod, ssr }: GlotstackProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
31
41
|
export declare const useGlotstack: () => ContextType;
|
|
32
42
|
export declare const useTranslations: (_options?: Record<never, never>) => ContextType;
|
|
33
43
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -32,8 +32,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.useTranslations = exports.useGlotstack = exports.GlotstackProvider = exports.access = exports.GlotstackContext = void 0;
|
|
35
|
+
exports.useTranslations = exports.useGlotstack = exports.GlotstackProvider = exports.access = exports.setLogLevel = exports.LogLevel = exports.GlotstackContext = void 0;
|
|
36
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
36
37
|
const React = __importStar(require("react"));
|
|
38
|
+
const object_1 = require("./util/object");
|
|
37
39
|
exports.GlotstackContext = React.createContext({
|
|
38
40
|
translations: {},
|
|
39
41
|
loadTranslations: () => { throw new Error('no import method set'); },
|
|
@@ -41,9 +43,46 @@ exports.GlotstackContext = React.createContext({
|
|
|
41
43
|
locale: null,
|
|
42
44
|
importMethod: (_locale) => { throw new Error('import method not set'); },
|
|
43
45
|
t: () => { throw new Error('import method not set'); },
|
|
46
|
+
ssr: false
|
|
44
47
|
});
|
|
48
|
+
var LogLevel;
|
|
49
|
+
(function (LogLevel) {
|
|
50
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
51
|
+
LogLevel[LogLevel["LOG"] = 1] = "LOG";
|
|
52
|
+
LogLevel[LogLevel["INFO"] = 2] = "INFO";
|
|
53
|
+
LogLevel[LogLevel["WARNING"] = 3] = "WARNING";
|
|
54
|
+
LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
|
|
55
|
+
})(LogLevel || (exports.LogLevel = LogLevel = {}));
|
|
56
|
+
const LogLevelToFunc = {
|
|
57
|
+
[LogLevel.DEBUG]: console.debug,
|
|
58
|
+
[LogLevel.INFO]: console.info,
|
|
59
|
+
[LogLevel.LOG]: console.log,
|
|
60
|
+
[LogLevel.WARNING]: console.warn,
|
|
61
|
+
[LogLevel.ERROR]: console.error,
|
|
62
|
+
};
|
|
63
|
+
let logLevel = LogLevel.DEBUG;
|
|
64
|
+
const setLogLevel = (level) => {
|
|
65
|
+
logLevel = level;
|
|
66
|
+
};
|
|
67
|
+
exports.setLogLevel = setLogLevel;
|
|
68
|
+
const makeLoggingFunction = (level) => (...args) => {
|
|
69
|
+
const func = LogLevelToFunc[level];
|
|
70
|
+
if (level < logLevel) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
return func(`[level=${level} logLevel=${logLevel}][glotstack.ai]`, ...args);
|
|
74
|
+
};
|
|
75
|
+
const logger = {
|
|
76
|
+
debug: makeLoggingFunction(LogLevel.DEBUG),
|
|
77
|
+
info: makeLoggingFunction(LogLevel.INFO),
|
|
78
|
+
warn: makeLoggingFunction(LogLevel.WARNING),
|
|
79
|
+
error: makeLoggingFunction(LogLevel.ERROR),
|
|
80
|
+
};
|
|
45
81
|
const access = (key, locale, translations) => {
|
|
46
82
|
var _a;
|
|
83
|
+
if (translations == null) {
|
|
84
|
+
return key;
|
|
85
|
+
}
|
|
47
86
|
const access = [...key.split('.')];
|
|
48
87
|
const localeTranslations = translations === null || translations === void 0 ? void 0 : translations[locale];
|
|
49
88
|
if (localeTranslations == null) {
|
|
@@ -56,61 +95,71 @@ const access = (key, locale, translations) => {
|
|
|
56
95
|
return ((_a = value === null || value === void 0 ? void 0 : value.value) !== null && _a !== void 0 ? _a : key);
|
|
57
96
|
};
|
|
58
97
|
exports.access = access;
|
|
59
|
-
const GlotstackProvider = ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod }) => {
|
|
98
|
+
const GlotstackProvider = ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod, ssr }) => {
|
|
60
99
|
if (initialLocale == null) {
|
|
61
100
|
throw new Error('initialLocale must be set');
|
|
62
101
|
}
|
|
63
102
|
const [locale, setLocale] = React.useState(initialLocale);
|
|
64
|
-
const
|
|
103
|
+
const translationsRef = React.useRef(initialTranslations || null);
|
|
65
104
|
const loadingRef = React.useRef({});
|
|
66
|
-
const loadTranslations = React.useCallback((locale) => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
-
var _a, _b;
|
|
105
|
+
const loadTranslations = React.useCallback((locale, opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
106
|
+
var _a, _b, _c, _d, _e, _f;
|
|
107
|
+
// TODO: if translations are loaded only reload if some condition is
|
|
68
108
|
try {
|
|
69
|
-
if (((_a = loadingRef.current) === null || _a === void 0 ? void 0 : _a[locale]) != null) {
|
|
109
|
+
if (((_a = loadingRef.current) === null || _a === void 0 ? void 0 : _a[locale]) != null && (opts === null || opts === void 0 ? void 0 : opts.force) != true) {
|
|
110
|
+
logger.debug('Waiting for translations already loading', locale);
|
|
70
111
|
return (yield ((_b = loadingRef.current) === null || _b === void 0 ? void 0 : _b[locale]));
|
|
71
112
|
}
|
|
72
|
-
|
|
113
|
+
if (((_c = translationsRef.current) === null || _c === void 0 ? void 0 : _c[locale]) != null && (opts === null || opts === void 0 ? void 0 : opts.force) != true) {
|
|
114
|
+
logger.debug('Skipping load for translations', locale, (_d = translationsRef.current) === null || _d === void 0 ? void 0 : _d[locale], translationsRef.current);
|
|
115
|
+
return (_e = translationsRef.current) === null || _e === void 0 ? void 0 : _e[locale];
|
|
116
|
+
}
|
|
117
|
+
if (loadingRef.current != null) {
|
|
118
|
+
logger.debug('Loading translations', locale, (0, object_1.merge)({}, (_f = translationsRef.current) !== null && _f !== void 0 ? _f : {}));
|
|
119
|
+
loadingRef.current[locale] = importMethod(locale);
|
|
120
|
+
}
|
|
73
121
|
const result = yield loadingRef.current[locale];
|
|
74
122
|
if (result == null) {
|
|
75
123
|
throw new Error(`Failed to load translation ${locale} ${JSON.stringify(result)}`);
|
|
76
124
|
}
|
|
77
|
-
|
|
125
|
+
if (translationsRef.current) {
|
|
126
|
+
translationsRef.current[locale] = result;
|
|
127
|
+
}
|
|
78
128
|
onTranslationLoaded === null || onTranslationLoaded === void 0 ? void 0 : onTranslationLoaded(locale, result);
|
|
79
129
|
return result;
|
|
80
130
|
}
|
|
81
131
|
catch (err) {
|
|
82
|
-
|
|
132
|
+
logger.error('Unable to import translations', err);
|
|
83
133
|
throw err;
|
|
84
134
|
}
|
|
85
|
-
}), [importMethod,
|
|
135
|
+
}), [importMethod, onTranslationLoaded]);
|
|
86
136
|
React.useEffect(() => {
|
|
87
137
|
const run = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
138
|
onLocaleChange === null || onLocaleChange === void 0 ? void 0 : onLocaleChange(locale);
|
|
89
|
-
|
|
139
|
+
yield loadTranslations(locale);
|
|
90
140
|
});
|
|
91
141
|
React.startTransition(() => {
|
|
92
142
|
run();
|
|
93
143
|
});
|
|
94
144
|
}, [locale]);
|
|
95
145
|
const context = React.useMemo(() => {
|
|
146
|
+
var _a;
|
|
96
147
|
return {
|
|
97
148
|
setLocale,
|
|
98
|
-
translations:
|
|
149
|
+
translations: (_a = translationsRef.current) !== null && _a !== void 0 ? _a : {},
|
|
99
150
|
locale,
|
|
100
151
|
importMethod,
|
|
101
152
|
loadTranslations,
|
|
102
153
|
t: (key, opts) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return (0, exports.access)(key, locale, translations !== null && translations !== void 0 ? translations : {});
|
|
110
|
-
}
|
|
154
|
+
var _a, _b, _c;
|
|
155
|
+
const resolvedLocale = (_a = opts === null || opts === void 0 ? void 0 : opts.locale) !== null && _a !== void 0 ? _a : locale;
|
|
156
|
+
loadTranslations(resolvedLocale);
|
|
157
|
+
return (0, exports.access)(key, (_b = opts === null || opts === void 0 ? void 0 : opts.locale) !== null && _b !== void 0 ? _b : locale, (_c = translationsRef.current) !== null && _c !== void 0 ? _c : {});
|
|
158
|
+
},
|
|
159
|
+
ssr: ssr == true,
|
|
111
160
|
};
|
|
112
|
-
}, [locale, importMethod
|
|
113
|
-
return
|
|
161
|
+
}, [locale, importMethod]);
|
|
162
|
+
return (0, jsx_runtime_1.jsx)(exports.GlotstackContext.Provider, { value: context, children: children });
|
|
114
163
|
};
|
|
115
164
|
exports.GlotstackProvider = GlotstackProvider;
|
|
116
165
|
const useGlotstack = () => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import globals from "globals";
|
|
2
|
+
import tseslint from "typescript-eslint";
|
|
3
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
4
|
+
import i18next from 'eslint-plugin-i18next';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export default defineConfig([
|
|
8
|
+
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], languageOptions: { globals: globals.browser } },
|
|
9
|
+
globalIgnores([
|
|
10
|
+
'node_modules/*', // ignore node modules
|
|
11
|
+
'**/*.d.ts', // ignore type definitions
|
|
12
|
+
]),
|
|
13
|
+
tseslint.configs.base,
|
|
14
|
+
i18next.configs['flat/recommended'],
|
|
15
|
+
]);
|
package/package.json
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glotstack",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"author": "JD Cumpson",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"private": false,
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"
|
|
10
|
+
"@eslint/js": "^9.26.0",
|
|
11
|
+
"commander": "^13.1.0",
|
|
12
|
+
"eslint": "^9.26.0",
|
|
13
|
+
"eslint-plugin-i18next": "^6.1.1",
|
|
14
|
+
"eslint-plugin-react": "^7.37.5",
|
|
15
|
+
"form-data": "^4.0.2",
|
|
16
|
+
"undici": "^7.9.0"
|
|
11
17
|
},
|
|
12
18
|
"devDependencies": {
|
|
13
19
|
"@types/js-yaml": "^4.0.9",
|
|
14
20
|
"@types/node": "^22.15.17",
|
|
15
|
-
"@types/react": "^
|
|
21
|
+
"@types/react": "^18.3.1",
|
|
22
|
+
"@types/react-dom": "^18.3.1",
|
|
23
|
+
"globals": "^16.1.0",
|
|
16
24
|
"js-yaml": "^4.1.0",
|
|
25
|
+
"nodemon": "^3.1.10",
|
|
17
26
|
"typescript": "5.4.4",
|
|
18
|
-
"typescript-eslint": "^8.
|
|
27
|
+
"typescript-eslint": "^8.32.1"
|
|
19
28
|
},
|
|
20
29
|
"peerDependencies": {
|
|
21
|
-
"
|
|
22
|
-
"react": "^18.3.1"
|
|
23
|
-
},
|
|
24
|
-
"resolutions": {
|
|
25
|
-
"react": "18.3.1"
|
|
30
|
+
"react": "^18.3.1",
|
|
31
|
+
"react-dom": "^18.3.1"
|
|
26
32
|
},
|
|
27
33
|
"bin": {
|
|
28
34
|
"glotstack": "dist/cli.js"
|
|
29
35
|
},
|
|
30
36
|
"scripts": {
|
|
31
37
|
"build": "tsc && scripts/fix-shebang.sh dist/cli.js",
|
|
38
|
+
"watch": "nodemon --watch src --ext ts,tsx,mjs,json --exec \"bash -c 'npm run build'\"",
|
|
32
39
|
"prepublishOnly": "yarn run build",
|
|
33
40
|
"glotstack": "node dist/cli.js"
|
|
34
41
|
}
|
package/src/cli.tsx
CHANGED
|
@@ -1,17 +1,155 @@
|
|
|
1
1
|
import { Command, program } from 'commander'
|
|
2
2
|
import path from 'path'
|
|
3
|
-
import { promises as fs } from 'fs'
|
|
3
|
+
import { promises as fs, createReadStream } from 'fs'
|
|
4
4
|
import { findGlotstackConfig } from './util/findConfig'
|
|
5
5
|
import { cwd } from 'process'
|
|
6
6
|
import { merge } from './util/object'
|
|
7
7
|
import { loadYaml } from './util/yaml'
|
|
8
|
+
import eslint from 'eslint'
|
|
9
|
+
import * as readline from "node:readline/promises"
|
|
10
|
+
import { stdin as input, stdout as output } from "node:process"
|
|
11
|
+
import { Translations } from 'src'
|
|
12
|
+
import { fetch } from 'undici'
|
|
13
|
+
import { FormData } from 'undici'
|
|
14
|
+
import { openAsBlob } from 'node:fs'
|
|
15
|
+
import { readdir, readFile, writeFile } from 'node:fs/promises'
|
|
16
|
+
import { resolve } from 'node:path'
|
|
17
|
+
|
|
18
|
+
const fetchGlotstack = async function <T>(url: string, apiKey: string, body: Record<string, any> | FormData, overrideHeaders?: Record<string, any>): Promise<T> {
|
|
19
|
+
console.info(`Extracting translations with: ${url}`)
|
|
20
|
+
|
|
21
|
+
const headers: Record<string, any> = {
|
|
22
|
+
'authorization': `Bearer ${apiKey}`,
|
|
23
|
+
...(overrideHeaders == null ? {} : overrideHeaders),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let payloadBody: FormData | string
|
|
27
|
+
|
|
28
|
+
if (!(body instanceof FormData)) {
|
|
29
|
+
headers['content-type'] = 'application/json'
|
|
30
|
+
payloadBody = JSON.stringify(body)
|
|
31
|
+
} else {
|
|
32
|
+
payloadBody = body
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(url, { method: 'POST', body: payloadBody, headers })
|
|
37
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)
|
|
38
|
+
return res.json() as T
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error('Fetch failed:', err)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function unflatten(flat: Record<string, any>): Record<string, any> {
|
|
46
|
+
const result: Record<string, any> = {}
|
|
47
|
+
|
|
48
|
+
for (const flatKey in flat) {
|
|
49
|
+
const parts = flatKey.split('.')
|
|
50
|
+
let current = result
|
|
51
|
+
|
|
52
|
+
parts.forEach((part, idx) => {
|
|
53
|
+
if (idx === parts.length - 1) {
|
|
54
|
+
current[part] = flat[flatKey]
|
|
55
|
+
} else {
|
|
56
|
+
if (!(part in current)) {
|
|
57
|
+
current[part] = {}
|
|
58
|
+
}
|
|
59
|
+
current = current[part]
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function resolveConfigAndOptions(options: Record<string, any>) {
|
|
68
|
+
|
|
69
|
+
const configPath = findGlotstackConfig(cwd())
|
|
70
|
+
let config = {}
|
|
71
|
+
|
|
72
|
+
if (configPath != null) {
|
|
73
|
+
console.info('Loading config file at ', configPath)
|
|
74
|
+
try {
|
|
75
|
+
const text = await fs.readFile(configPath, 'utf-8')
|
|
76
|
+
config = JSON.parse(text)
|
|
77
|
+
console.info('Loaded config file', config)
|
|
78
|
+
} catch (err) {
|
|
79
|
+
//pass
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if ('outputLocales' in options) {
|
|
84
|
+
if ((options.outputLocales as string[]).includes('en-US')) {
|
|
85
|
+
console.warn('en-US detected in outputLocales, removing')
|
|
86
|
+
options.outputLocales = options.outputLocales.filter((x: string) => x !== 'en-US')
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return merge(config, options)
|
|
91
|
+
}
|
|
8
92
|
|
|
9
93
|
|
|
10
94
|
async function run(args: string[]) {
|
|
95
|
+
program
|
|
96
|
+
.command('extract-translations')
|
|
97
|
+
.description('extract translations from all compatible source files.')
|
|
98
|
+
.option('--source-path [path]', 'to source files root directory', '.')
|
|
99
|
+
.option('--api-origin [url]', 'glotstack api origin', process.env.GLOTSTACK_HOST ?? 'https://glotstack.ai')
|
|
100
|
+
.option('--api-key [key]', 'api key for glotstack.ai')
|
|
101
|
+
.option('--yes', 'skip confirm checks', false)
|
|
102
|
+
.action(async (options: Record<string, any>) => {
|
|
103
|
+
if (!options.apiOrigin) {
|
|
104
|
+
throw new Error('apiOrigin must be specified')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const linter = new eslint.ESLint({ overrideConfigFile: path.join(__dirname, '..', 'eslint-raw-string.mjs') })
|
|
108
|
+
const results = await linter.lintFiles(["./**/*"])
|
|
109
|
+
const filesWithIssues = results
|
|
110
|
+
.filter((r) => r.errorCount + r.warningCount > 0)
|
|
111
|
+
.map((r) => r.filePath)
|
|
112
|
+
|
|
113
|
+
const rl = readline.createInterface({ input, output })
|
|
114
|
+
const askToSend = async (): Promise<boolean> => {
|
|
115
|
+
if (options.yes) {
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
const response = await rl.question(`Your source are going to be sent to our LLM -- they should not contain any secrets. Proceed? (yes/no):`)
|
|
119
|
+
if (response === 'yes') {
|
|
120
|
+
return true
|
|
121
|
+
} else if (response !== 'no') {
|
|
122
|
+
console.error('Please respond with yes or no.')
|
|
123
|
+
return askToSend()
|
|
124
|
+
} else {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const send = await askToSend()
|
|
130
|
+
if (send) {
|
|
131
|
+
console.info('Sending files to LLM')
|
|
132
|
+
const url = `${options.apiOrigin}/uploads/translations/extract`
|
|
133
|
+
const form = new FormData()
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < filesWithIssues.length; i++) {
|
|
136
|
+
const filePath = filesWithIssues[i]
|
|
137
|
+
form.append(`file_${i}`, await openAsBlob(filePath), filePath)
|
|
138
|
+
console.debug(`Uploading file: ${filePath}`)
|
|
139
|
+
}
|
|
140
|
+
const data = await fetchGlotstack<{ translations: { name: string; modified_source: { url: string } }[] }>(url, options.apiKey, form)
|
|
141
|
+
data.translations.map(elem => console.info(`${elem.name}:\n ${elem.modified_source.url}\n\n`))
|
|
142
|
+
rl.close()
|
|
143
|
+
} else {
|
|
144
|
+
rl.close()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
11
148
|
program
|
|
12
149
|
.command('get-translations')
|
|
150
|
+
.description('fetch translations for all [output-locals...]. Use .glotstack.json for repeatable results.')
|
|
13
151
|
.option('--source-path [path]', 'path to en-US.json (or your canonical source json)')
|
|
14
|
-
.option('--api-origin [url]', 'glotstack api origin', '
|
|
152
|
+
.option('--api-origin [url]', 'glotstack api origin', process.env.GLOTSTACK_HOST ?? 'https://glotstack.ai')
|
|
15
153
|
.option('--output-dir [path]', 'path to output directory')
|
|
16
154
|
.option('--api-key [key]', 'api key for glotstack.ai')
|
|
17
155
|
.option('--project-id [id]', '(optional) specific project to use')
|
|
@@ -26,77 +164,95 @@ async function run(args: string[]) {
|
|
|
26
164
|
const text = await fs.readFile(configPath, 'utf-8')
|
|
27
165
|
config = JSON.parse(text)
|
|
28
166
|
console.info('Loaded config file', config)
|
|
29
|
-
} catch(err) {
|
|
167
|
+
} catch (err) {
|
|
30
168
|
//pass
|
|
31
169
|
}
|
|
32
170
|
}
|
|
33
171
|
|
|
34
|
-
const resolved =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if ((resolved.outputLocales as string[]).includes('en-US')) {
|
|
38
|
-
console.warn('en-US detected in outputLocales, removing');
|
|
39
|
-
resolved.outputLocales = resolved.outputLocales.filter((x: string) => x !== 'en-US')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!sourcePath) {
|
|
172
|
+
const resolved = await resolveConfigAndOptions({...options, outputLocales: outputLocales})
|
|
173
|
+
if (!resolved.sourcePath) {
|
|
43
174
|
throw new Error('sourcePath must be specified')
|
|
44
175
|
}
|
|
45
|
-
if (!apiOrigin) {
|
|
176
|
+
if (!resolved.apiOrigin) {
|
|
46
177
|
throw new Error('apiOrigin must be specified')
|
|
47
178
|
}
|
|
48
|
-
if (!outputDir) {
|
|
179
|
+
if (!resolved.outputDir) {
|
|
49
180
|
throw new Error('outputDir must be specified')
|
|
50
181
|
}
|
|
51
182
|
|
|
52
|
-
const
|
|
53
|
-
console.info(`Fetching translations from: ${url}`)
|
|
54
|
-
|
|
55
|
-
const absPath = path.resolve(sourcePath)
|
|
183
|
+
const absPath = path.resolve(resolved.sourcePath)
|
|
56
184
|
const fileContent = await fs.readFile(absPath, 'utf-8')
|
|
57
185
|
|
|
58
186
|
let json = null
|
|
59
187
|
try {
|
|
60
188
|
json = loadYaml(fileContent)
|
|
61
|
-
} catch(err) {
|
|
189
|
+
} catch (err) {
|
|
62
190
|
try {
|
|
63
191
|
json = JSON.parse(fileContent)
|
|
64
|
-
} catch(err) {
|
|
192
|
+
} catch (err) {
|
|
65
193
|
console.error('Unable to parse source file ', absPath, err)
|
|
66
|
-
throw err
|
|
194
|
+
throw err
|
|
67
195
|
}
|
|
68
196
|
}
|
|
69
197
|
|
|
70
198
|
const body = {
|
|
71
199
|
locales: resolved.outputLocales,
|
|
72
200
|
translations: json,
|
|
73
|
-
...{ ... (projectId != null ? {projectId} : {})},
|
|
201
|
+
...{ ... (resolved.projectId != null ? { projectId: resolved.projectId } : {}) },
|
|
74
202
|
}
|
|
75
203
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
204
|
+
const url = `${options.apiOrigin}/api/translations`
|
|
205
|
+
const data = await fetchGlotstack<{ data: Translations }>(url, resolved.apiKey, body)
|
|
206
|
+
console.info('Received translations:', data)
|
|
207
|
+
Object.entries(data.data).map(([key, val]) => {
|
|
208
|
+
const p = `${resolved.outputDir}/${key}.json`
|
|
209
|
+
console.info(`Writing file ${p}`)
|
|
210
|
+
fs.writeFile(`${resolved.outputDir}/${key}.json`, JSON.stringify(val, null, 2))
|
|
211
|
+
})
|
|
212
|
+
})
|
|
80
213
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
fs.writeFile(`${outputDir}/${key}.json`, JSON.stringify(val, null, 2))
|
|
91
|
-
})
|
|
92
|
-
// fs.writeFile(`${outputDir}/source.json`, JSON.stringify(json, null, 2))
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.error('Fetch failed:', err)
|
|
95
|
-
process.exit(1)
|
|
214
|
+
program
|
|
215
|
+
.command('format-json')
|
|
216
|
+
.description('format files in --source-path [path] to nested (not flat)')
|
|
217
|
+
.option('--source-path [path]', 'to source files root directory', '.')
|
|
218
|
+
.option('--yes', 'skip confirm checks', false)
|
|
219
|
+
.action(async (options: Record<string, any>) => {
|
|
220
|
+
|
|
221
|
+
if (!options.sourcePath) {
|
|
222
|
+
throw new Error('sourcePath must be specified')
|
|
96
223
|
}
|
|
224
|
+
const rl = readline.createInterface({ input, output })
|
|
225
|
+
const askToSend = async (): Promise<boolean> => {
|
|
226
|
+
if (options.yes) {
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
const response = await rl.question(`This will update your source files -- have you checked them into SCM/git? Type yes to proceed (yes/no):`)
|
|
230
|
+
if (response === 'yes') {
|
|
231
|
+
return true
|
|
232
|
+
} else if (response !== 'no') {
|
|
233
|
+
console.error('Please respond with yes or no.')
|
|
234
|
+
return askToSend()
|
|
235
|
+
} else {
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const yes = await askToSend()
|
|
240
|
+
if (yes) {
|
|
241
|
+
const files = await readdir(options.sourcePath)
|
|
242
|
+
for (let i = 0; i < (await files).length; i++) {
|
|
243
|
+
const fp = resolve(options.sourcePath, files[i])
|
|
244
|
+
const text = await readFile(fp, 'utf-8')
|
|
245
|
+
const json = JSON.parse(text)
|
|
246
|
+
const formatted = JSON.stringify(unflatten(json), null, 2)
|
|
247
|
+
await writeFile(fp, formatted)
|
|
248
|
+
}
|
|
249
|
+
rl.close()
|
|
250
|
+
}
|
|
251
|
+
rl.close()
|
|
97
252
|
|
|
98
253
|
})
|
|
99
254
|
|
|
255
|
+
|
|
100
256
|
await program.parseAsync(args)
|
|
101
257
|
}
|
|
102
258
|
|
package/src/index.tsx
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import { merge
|
|
2
|
+
import { merge } from './util/object'
|
|
3
|
+
import { debug } from 'node:console'
|
|
3
4
|
|
|
4
|
-
export type LocaleRegion =
|
|
5
|
-
| 'en'
|
|
6
|
-
| 'en-US'
|
|
7
|
-
| 'en-US-genz'
|
|
8
|
-
| 'fr-FR'
|
|
9
|
-
| 'de-DE'
|
|
10
|
-
| 'nl-NL'
|
|
11
|
-
| 'jp-JP'
|
|
12
|
-
| string
|
|
5
|
+
export type LocaleRegion = string
|
|
13
6
|
|
|
14
7
|
|
|
15
8
|
export interface TranslationLeaf {
|
|
@@ -21,13 +14,15 @@ export interface Translations {
|
|
|
21
14
|
[key: string]: Translations | TranslationLeaf
|
|
22
15
|
}
|
|
23
16
|
|
|
17
|
+
|
|
24
18
|
export interface ContextType {
|
|
25
19
|
translations: Translations
|
|
26
20
|
locale: string | null
|
|
27
21
|
loadTranslations: (locale: LocaleRegion) => Promise<Translations>
|
|
28
22
|
setLocale: (locale: LocaleRegion) => void
|
|
29
|
-
importMethod: (locale: LocaleRegion) => Promise<Translations>
|
|
23
|
+
importMethod: (locale: LocaleRegion) => Promise<Translations>
|
|
30
24
|
t: (key: string, options?: { locale?: LocaleRegion }) => string
|
|
25
|
+
ssr: boolean
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
export const GlotstackContext = React.createContext<ContextType>({
|
|
@@ -37,18 +32,61 @@ export const GlotstackContext = React.createContext<ContextType>({
|
|
|
37
32
|
locale: null,
|
|
38
33
|
importMethod: (_locale: LocaleRegion) => { throw new Error('import method not set') },
|
|
39
34
|
t: () => { throw new Error('import method not set') },
|
|
35
|
+
ssr: false
|
|
40
36
|
})
|
|
41
37
|
|
|
42
38
|
interface GlotstackProviderProps {
|
|
43
39
|
children: React.ReactNode
|
|
44
|
-
initialTranslations?: Translations
|
|
40
|
+
initialTranslations?: Record<string, Translations>
|
|
45
41
|
initialLocale?: LocaleRegion
|
|
46
42
|
onTranslationLoaded?: (locale: LocaleRegion, translations: Translations) => void
|
|
47
43
|
onLocaleChange?: (locale: LocaleRegion) => void
|
|
48
|
-
importMethod:
|
|
44
|
+
importMethod: ContextType['importMethod']
|
|
45
|
+
ssr?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export enum LogLevel {
|
|
49
|
+
DEBUG = 0,
|
|
50
|
+
LOG = 1,
|
|
51
|
+
INFO = 2,
|
|
52
|
+
WARNING = 3,
|
|
53
|
+
ERROR = 4,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const LogLevelToFunc: Record<LogLevel, (...args: Parameters<typeof console.info>) => void> = {
|
|
57
|
+
[LogLevel.DEBUG]: console.debug,
|
|
58
|
+
[LogLevel.INFO]: console.info,
|
|
59
|
+
[LogLevel.LOG]: console.log,
|
|
60
|
+
[LogLevel.WARNING]: console.warn,
|
|
61
|
+
[LogLevel.ERROR]: console.error,
|
|
62
|
+
} as const
|
|
63
|
+
|
|
64
|
+
let logLevel: LogLevel = LogLevel.DEBUG
|
|
65
|
+
|
|
66
|
+
export const setLogLevel = (level: LogLevel) => {
|
|
67
|
+
logLevel = level
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const makeLoggingFunction = (level: LogLevel) => (...args: Parameters<typeof console.info>) => {
|
|
71
|
+
const func = LogLevelToFunc[level]
|
|
72
|
+
if (level < logLevel) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
return func(`[level=${level} logLevel=${logLevel}][glotstack.ai]`, ...args)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const logger = {
|
|
79
|
+
debug: makeLoggingFunction(LogLevel.DEBUG),
|
|
80
|
+
info: makeLoggingFunction(LogLevel.INFO),
|
|
81
|
+
warn: makeLoggingFunction(LogLevel.WARNING),
|
|
82
|
+
error: makeLoggingFunction(LogLevel.ERROR),
|
|
83
|
+
|
|
49
84
|
}
|
|
50
85
|
|
|
51
86
|
export const access = (key: string, locale: LocaleRegion, translations: Translations) => {
|
|
87
|
+
if (translations == null) {
|
|
88
|
+
return key
|
|
89
|
+
}
|
|
52
90
|
const access = [...key.split('.')] as [LocaleRegion, ...string[]]
|
|
53
91
|
const localeTranslations = translations?.[locale]
|
|
54
92
|
|
|
@@ -64,39 +102,49 @@ export const access = (key: string, locale: LocaleRegion, translations: Translat
|
|
|
64
102
|
return (value?.value ?? key) as string
|
|
65
103
|
}
|
|
66
104
|
|
|
67
|
-
export const GlotstackProvider = ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod }: GlotstackProviderProps) => {
|
|
105
|
+
export const GlotstackProvider = ({ children, initialLocale, initialTranslations, onLocaleChange, onTranslationLoaded, importMethod, ssr}: GlotstackProviderProps) => {
|
|
68
106
|
if (initialLocale == null) {
|
|
69
107
|
throw new Error('initialLocale must be set')
|
|
70
108
|
}
|
|
71
|
-
|
|
72
109
|
const [locale, setLocale] = React.useState<LocaleRegion>(initialLocale)
|
|
73
|
-
const
|
|
110
|
+
const translationsRef = React.useRef<Record<string, Translations>|null>(initialTranslations || null)
|
|
74
111
|
const loadingRef = React.useRef<Record<string, Promise<Translations>>>({})
|
|
75
112
|
|
|
76
|
-
const loadTranslations = React.useCallback(async (locale: string) => {
|
|
113
|
+
const loadTranslations = React.useCallback(async (locale: string, opts?: {force?: boolean}) => {
|
|
114
|
+
// TODO: if translations are loaded only reload if some condition is
|
|
77
115
|
try {
|
|
78
|
-
if (loadingRef.current?.[locale] != null) {
|
|
116
|
+
if (loadingRef.current?.[locale] != null && opts?.force != true) {
|
|
117
|
+
logger.debug('Waiting for translations already loading', locale)
|
|
79
118
|
return (await loadingRef.current?.[locale])
|
|
80
119
|
}
|
|
81
|
-
|
|
120
|
+
if (translationsRef.current?.[locale] != null && opts?.force != true) {
|
|
121
|
+
logger.debug('Skipping load for translations', locale, translationsRef.current?.[locale], translationsRef.current)
|
|
122
|
+
return translationsRef.current?.[locale]
|
|
123
|
+
}
|
|
124
|
+
if (loadingRef.current != null) {
|
|
125
|
+
logger.debug('Loading translations', locale, merge({}, translationsRef.current ?? {}))
|
|
126
|
+
loadingRef.current[locale] = importMethod(locale)
|
|
127
|
+
}
|
|
82
128
|
const result = await loadingRef.current[locale]
|
|
83
129
|
|
|
84
130
|
if (result == null) {
|
|
85
131
|
throw new Error(`Failed to load translation ${locale} ${JSON.stringify(result)}`)
|
|
86
132
|
}
|
|
87
|
-
|
|
133
|
+
if (translationsRef.current) {
|
|
134
|
+
translationsRef.current[locale] = result
|
|
135
|
+
}
|
|
88
136
|
onTranslationLoaded?.(locale, result)
|
|
89
137
|
return result
|
|
90
138
|
} catch (err) {
|
|
91
|
-
|
|
139
|
+
logger.error('Unable to import translations', err)
|
|
92
140
|
throw err
|
|
93
141
|
}
|
|
94
|
-
}, [importMethod,
|
|
142
|
+
}, [importMethod, onTranslationLoaded])
|
|
95
143
|
|
|
96
144
|
React.useEffect(() => {
|
|
97
145
|
const run = async () => {
|
|
98
146
|
onLocaleChange?.(locale)
|
|
99
|
-
|
|
147
|
+
await loadTranslations(locale)
|
|
100
148
|
}
|
|
101
149
|
React.startTransition(() => {
|
|
102
150
|
run()
|
|
@@ -106,22 +154,18 @@ export const GlotstackProvider = ({ children, initialLocale, initialTranslations
|
|
|
106
154
|
const context = React.useMemo(() => {
|
|
107
155
|
return {
|
|
108
156
|
setLocale,
|
|
109
|
-
translations:
|
|
157
|
+
translations: translationsRef.current ?? {},
|
|
110
158
|
locale,
|
|
111
159
|
importMethod,
|
|
112
160
|
loadTranslations,
|
|
113
161
|
t: (key: string, opts?: { locale?: LocaleRegion }) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}, [locale])
|
|
120
|
-
return access(key, locale, translations ?? {})
|
|
121
|
-
}
|
|
122
|
-
|
|
162
|
+
const resolvedLocale = opts?.locale ?? locale
|
|
163
|
+
loadTranslations(resolvedLocale)
|
|
164
|
+
return access(key, opts?.locale ?? locale, translationsRef.current ?? {})
|
|
165
|
+
},
|
|
166
|
+
ssr: ssr == true,
|
|
123
167
|
}
|
|
124
|
-
}, [locale, importMethod
|
|
168
|
+
}, [locale, importMethod])
|
|
125
169
|
|
|
126
170
|
return <GlotstackContext.Provider value={context}>
|
|
127
171
|
{children}
|
|
@@ -132,7 +176,6 @@ export const useGlotstack = () => {
|
|
|
132
176
|
return React.useContext(GlotstackContext)
|
|
133
177
|
}
|
|
134
178
|
|
|
135
|
-
|
|
136
179
|
export const useTranslations = (_options?: Record<never, never>) => {
|
|
137
180
|
const context = React.useContext(GlotstackContext)
|
|
138
181
|
return context
|