@scoutello/i18n-magic 0.64.0 → 0.66.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.
Files changed (58) hide show
  1. package/README.md +13 -7
  2. package/dist/cli.js +20 -15
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/check-missing.d.ts +11 -1
  5. package/dist/commands/check-missing.d.ts.map +1 -1
  6. package/dist/commands/check-missing.js +56 -96
  7. package/dist/commands/check-missing.js.map +1 -1
  8. package/dist/commands/replace.d.ts +6 -0
  9. package/dist/commands/replace.d.ts.map +1 -1
  10. package/dist/commands/replace.js +48 -29
  11. package/dist/commands/replace.js.map +1 -1
  12. package/dist/commands/scan.d.ts.map +1 -1
  13. package/dist/commands/scan.js +114 -80
  14. package/dist/commands/scan.js.map +1 -1
  15. package/dist/commands/sync-locales.d.ts.map +1 -1
  16. package/dist/commands/sync-locales.js +90 -50
  17. package/dist/commands/sync-locales.js.map +1 -1
  18. package/dist/index.d.ts +2 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/languges.d.ts.map +1 -1
  23. package/dist/lib/languges.js +20 -0
  24. package/dist/lib/languges.js.map +1 -1
  25. package/dist/lib/locale-storage.d.ts +7 -0
  26. package/dist/lib/locale-storage.d.ts.map +1 -0
  27. package/dist/lib/locale-storage.js +49 -0
  28. package/dist/lib/locale-storage.js.map +1 -0
  29. package/dist/lib/translation-response.d.ts +8 -0
  30. package/dist/lib/translation-response.d.ts.map +1 -0
  31. package/dist/lib/translation-response.js +43 -0
  32. package/dist/lib/translation-response.js.map +1 -0
  33. package/dist/lib/types.d.ts +1 -1
  34. package/dist/lib/types.d.ts.map +1 -1
  35. package/dist/lib/update-translation-key.d.ts +16 -0
  36. package/dist/lib/update-translation-key.d.ts.map +1 -0
  37. package/dist/lib/update-translation-key.js +67 -0
  38. package/dist/lib/update-translation-key.js.map +1 -0
  39. package/dist/lib/utils.d.ts +2 -0
  40. package/dist/lib/utils.d.ts.map +1 -1
  41. package/dist/lib/utils.js +69 -51
  42. package/dist/lib/utils.js.map +1 -1
  43. package/dist/mcp-server.js +28 -79
  44. package/dist/mcp-server.js.map +1 -1
  45. package/package.json +5 -9
  46. package/src/cli.ts +26 -18
  47. package/src/commands/check-missing.ts +83 -137
  48. package/src/commands/replace.ts +81 -43
  49. package/src/commands/scan.ts +130 -108
  50. package/src/commands/sync-locales.ts +125 -86
  51. package/src/index.ts +6 -1
  52. package/src/lib/languges.ts +20 -0
  53. package/src/lib/locale-storage.ts +76 -0
  54. package/src/lib/translation-response.ts +59 -0
  55. package/src/lib/types.ts +1 -1
  56. package/src/lib/update-translation-key.ts +119 -0
  57. package/src/lib/utils.ts +92 -76
  58. package/src/mcp-server.ts +31 -112
@@ -1 +1 @@
1
- {"version":3,"file":"languges.js","sourceRoot":"","sources":["../../src/lib/languges.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;QACE,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,QAAQ;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,kBAAkB;QACzB,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,IAAI;KACZ;IACD;QACE,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,IAAI;KACZ;CACF,CAAA"}
1
+ {"version":3,"file":"languges.js","sourceRoot":"","sources":["../../src/lib/languges.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Configuration } from "./types.js";
2
+ export declare class LocaleConfigurationError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare const resolveConfiguredLocale: (config: Pick<Configuration, "defaultLocale" | "locales">, requested?: string) => string;
6
+ export declare const resolveLocaleTemplatePath: (template: string, locale: string, namespace: string) => string;
7
+ //# sourceMappingURL=locale-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-storage.d.ts","sourceRoot":"","sources":["../../src/lib/locale-storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,eAAO,MAAM,uBAAuB,GAClC,QAAQ,IAAI,CAAC,aAAa,EAAE,eAAe,GAAG,SAAS,CAAC,EACxD,YAAY,MAAM,WAWnB,CAAA;AA2BD,eAAO,MAAM,yBAAyB,GACpC,UAAU,MAAM,EAChB,QAAQ,MAAM,EACd,WAAW,MAAM,WAsBlB,CAAA"}
@@ -0,0 +1,49 @@
1
+ import path from "node:path";
2
+ export class LocaleConfigurationError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "LocaleConfigurationError";
6
+ }
7
+ }
8
+ export const resolveConfiguredLocale = (config, requested) => {
9
+ const locale = requested ?? config.defaultLocale;
10
+ if (!config.locales.includes(locale)) {
11
+ throw new LocaleConfigurationError(`Unsupported locale "${locale}". Expected one of: ${config.locales.join(", ")}.`);
12
+ }
13
+ return locale;
14
+ };
15
+ const getConfiguredBasePath = (template) => {
16
+ const placeholderIndexes = [
17
+ template.indexOf("{{lng}}"),
18
+ template.indexOf("{{ns}}"),
19
+ ].filter((index) => index >= 0);
20
+ if (placeholderIndexes.length === 0) {
21
+ return path.dirname(path.resolve(template));
22
+ }
23
+ const firstPlaceholder = Math.min(...placeholderIndexes);
24
+ const staticPrefix = template.slice(0, firstPlaceholder);
25
+ if (staticPrefix.length === 0 ||
26
+ staticPrefix.endsWith(path.sep) ||
27
+ staticPrefix.endsWith("/") ||
28
+ staticPrefix.endsWith("\\")) {
29
+ return path.resolve(staticPrefix || ".");
30
+ }
31
+ return path.dirname(path.resolve(staticPrefix));
32
+ };
33
+ export const resolveLocaleTemplatePath = (template, locale, namespace) => {
34
+ const configuredBase = getConfiguredBasePath(template);
35
+ const expandedPath = template
36
+ .split("{{lng}}")
37
+ .join(locale)
38
+ .split("{{ns}}")
39
+ .join(namespace);
40
+ const resolvedPath = path.resolve(expandedPath);
41
+ const relativePath = path.relative(configuredBase, resolvedPath);
42
+ if (relativePath === ".." ||
43
+ relativePath.startsWith(`..${path.sep}`) ||
44
+ path.isAbsolute(relativePath)) {
45
+ throw new LocaleConfigurationError("Expanded locale path escapes the configured storage base.");
46
+ }
47
+ return resolvedPath;
48
+ };
49
+ //# sourceMappingURL=locale-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-storage.js","sourceRoot":"","sources":["../../src/lib/locale-storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAA;IACxC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,MAAwD,EACxD,SAAkB,EAClB,EAAE;IACF,MAAM,MAAM,GAAG,SAAS,IAAI,MAAM,CAAC,aAAa,CAAA;IAEhD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,wBAAwB,CAChC,uBAAuB,MAAM,uBAAuB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACjF,CAAA;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,qBAAqB,GAAG,CAAC,QAAgB,EAAE,EAAE;IACjD,MAAM,kBAAkB,GAAG;QACzB,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;QAC3B,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;KAC3B,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,CAAA;IAE/B,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,CAAA;IACxD,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAA;IAExD,IACE,YAAY,CAAC,MAAM,KAAK,CAAC;QACzB,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QAC/B,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC;QAC1B,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAC3B,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC,CAAA;IAC1C,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAA;AACjD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,QAAgB,EAChB,MAAc,EACd,SAAiB,EACjB,EAAE;IACF,MAAM,cAAc,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAA;IACtD,MAAM,YAAY,GAAG,QAAQ;SAC1B,KAAK,CAAC,SAAS,CAAC;SAChB,IAAI,CAAC,MAAM,CAAC;SACZ,KAAK,CAAC,QAAQ,CAAC;SACf,IAAI,CAAC,SAAS,CAAC,CAAA;IAClB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC,CAAA;IAEhE,IACE,YAAY,KAAK,IAAI;QACrB,YAAY,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QACxC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAC7B,CAAC;QACD,MAAM,IAAI,wBAAwB,CAChC,2DAA2D,CAC5D,CAAA;IACH,CAAC;IAED,OAAO,YAAY,CAAA;AACrB,CAAC,CAAA"}
@@ -0,0 +1,8 @@
1
+ export declare class TranslationResponseError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class TranslationProviderUnavailableError extends Error {
5
+ constructor();
6
+ }
7
+ export declare const parseTranslationResponse: (content: string | null, requestedKeys: string[]) => Record<string, string>;
8
+ //# sourceMappingURL=translation-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translation-response.d.ts","sourceRoot":"","sources":["../../src/lib/translation-response.ts"],"names":[],"mappings":"AAAA,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mCAAoC,SAAQ,KAAK;;CAO7D;AAED,eAAO,MAAM,wBAAwB,GACnC,SAAS,MAAM,GAAG,IAAI,EACtB,eAAe,MAAM,EAAE,KACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAuCvB,CAAA"}
@@ -0,0 +1,43 @@
1
+ export class TranslationResponseError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "TranslationResponseError";
5
+ }
6
+ }
7
+ export class TranslationProviderUnavailableError extends Error {
8
+ constructor() {
9
+ super("A translation provider is required for this operation, but no provider credentials are configured.");
10
+ this.name = "TranslationProviderUnavailableError";
11
+ }
12
+ }
13
+ export const parseTranslationResponse = (content, requestedKeys) => {
14
+ let parsed;
15
+ try {
16
+ parsed = content === null ? null : JSON.parse(content);
17
+ }
18
+ catch {
19
+ throw new TranslationResponseError("Translation provider returned malformed JSON.");
20
+ }
21
+ if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
22
+ throw new TranslationResponseError("Translation provider returned a value that is not a JSON object.");
23
+ }
24
+ const response = parsed;
25
+ const requested = new Set(requestedKeys);
26
+ const responseKeys = Object.keys(response);
27
+ const missingKeys = requestedKeys.filter((key) => !Object.hasOwn(response, key));
28
+ const extraKeys = responseKeys.filter((key) => !requested.has(key));
29
+ const invalidValueKeys = responseKeys.filter((key) => typeof response[key] !== "string");
30
+ if (missingKeys.length > 0 || extraKeys.length > 0 || invalidValueKeys.length > 0) {
31
+ const problems = [];
32
+ if (missingKeys.length > 0)
33
+ problems.push(`missing keys: ${missingKeys.join(", ")}`);
34
+ if (extraKeys.length > 0)
35
+ problems.push(`extra keys: ${extraKeys.join(", ")}`);
36
+ if (invalidValueKeys.length > 0) {
37
+ problems.push(`non-string values: ${invalidValueKeys.join(", ")}`);
38
+ }
39
+ throw new TranslationResponseError(`Translation provider returned an invalid key/value shape (${problems.join("; ")}).`);
40
+ }
41
+ return response;
42
+ };
43
+ //# sourceMappingURL=translation-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translation-response.js","sourceRoot":"","sources":["../../src/lib/translation-response.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAA;IACxC,CAAC;CACF;AAED,MAAM,OAAO,mCAAoC,SAAQ,KAAK;IAC5D;QACE,KAAK,CACH,oGAAoG,CACrG,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,qCAAqC,CAAA;IACnD,CAAC;CACF;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,OAAsB,EACtB,aAAuB,EACC,EAAE;IAC1B,IAAI,MAAe,CAAA;IAEnB,IAAI,CAAC;QACH,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,wBAAwB,CAChC,+CAA+C,CAChD,CAAA;IACH,CAAC;IAED,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC3E,MAAM,IAAI,wBAAwB,CAChC,kEAAkE,CACnE,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,MAAiC,CAAA;IAClD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAA;IACxC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC1C,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAA;IAChF,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IACnE,MAAM,gBAAgB,GAAG,YAAY,CAAC,MAAM,CAC1C,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,QAAQ,CAAC,GAAG,CAAC,KAAK,QAAQ,CAC3C,CAAA;IAED,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClF,MAAM,QAAQ,GAAa,EAAE,CAAA;QAC7B,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACpF,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,eAAe,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC9E,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,CAAC,sBAAsB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACpE,CAAC;QACD,MAAM,IAAI,wBAAwB,CAChC,6DAA6D,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CACrF,CAAA;IACH,CAAC;IAED,OAAO,QAAkC,CAAA;AAC3C,CAAC,CAAA"}
@@ -24,7 +24,7 @@ export interface Configuration {
24
24
  export interface CommandType {
25
25
  name: string;
26
26
  description: string;
27
- action: (config: Configuration, ...args: any[]) => Promise<void>;
27
+ action: (config: Configuration, ...args: any[]) => Promise<unknown>;
28
28
  }
29
29
  export {};
30
30
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAA;AAChC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAE3D,KAAK,KAAK,GACN,SAAS,GACT,0BAA0B,GAC1B,kBAAkB,GAClB,uBAAuB,CAAA;AAE3B,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EACJ,MAAM,GACN,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IAC5E,QAAQ,EACJ,MAAM,GACN,CAAC,CACC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,YAAY,EAAE,CAAC,MAAM,GAAG,iBAAiB,CAAC,EAAE,CAAA;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4BAA4B,CAAC,EAAE,OAAO,CAAA;IACtC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACjE"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAA;AAChC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAE3D,KAAK,KAAK,GACN,SAAS,GACT,0BAA0B,GAC1B,kBAAkB,GAClB,uBAAuB,CAAA;AAE3B,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EACJ,MAAM,GACN,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IAC5E,QAAQ,EACJ,MAAM,GACN,CAAC,CACC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,YAAY,EAAE,CAAC,MAAM,GAAG,iBAAiB,CAAC,EAAE,CAAA;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4BAA4B,CAAC,EAAE,OAAO,CAAA;IACtC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACpE"}
@@ -0,0 +1,16 @@
1
+ import type { Configuration } from "./types.js";
2
+ export interface UpdateTranslationKeyResult {
3
+ key: string;
4
+ value: string;
5
+ sourceLocale: string;
6
+ targetNamespaces: string[];
7
+ updatedLocales: string[];
8
+ pendingLocales: string[];
9
+ }
10
+ export declare const updateTranslationKeyOperation: ({ config, key, value, language, }: {
11
+ config: Configuration;
12
+ key: string;
13
+ value: string;
14
+ language?: string;
15
+ }) => Promise<UpdateTranslationKeyResult>;
16
+ //# sourceMappingURL=update-translation-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-translation-key.d.ts","sourceRoot":"","sources":["../../src/lib/update-translation-key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAQ/C,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB;AAED,eAAO,MAAM,6BAA6B,GAAU,mCAKjD;IACD,MAAM,EAAE,aAAa,CAAA;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,KAAG,OAAO,CAAC,0BAA0B,CA2FrC,CAAA"}
@@ -0,0 +1,67 @@
1
+ import { resolveConfiguredLocale } from "./locale-storage.js";
2
+ import { loadLocalesFile, translateKey, writeLocalesFile, } from "./utils.js";
3
+ export const updateTranslationKeyOperation = async ({ config, key, value, language, }) => {
4
+ const sourceLocale = resolveConfiguredLocale(config, language);
5
+ const sourceFiles = await Promise.all(config.namespaces.map(async (namespace) => ({
6
+ namespace,
7
+ translations: await loadLocalesFile(config.loadPath, sourceLocale, namespace, { silent: true }),
8
+ })));
9
+ const targetNamespaces = sourceFiles
10
+ .filter(({ translations }) => Object.hasOwn(translations, key))
11
+ .map(({ namespace }) => namespace);
12
+ if (targetNamespaces.length === 0) {
13
+ throw new Error(`Key "${key}" does not exist in the ${sourceLocale} translation files. Use add_translation_key to create it.`);
14
+ }
15
+ const pendingLocales = config.openai
16
+ ? []
17
+ : config.locales.filter((locale) => locale !== sourceLocale);
18
+ const updatedLocales = config.openai ? [...config.locales] : [sourceLocale];
19
+ const translatedValues = {
20
+ [sourceLocale]: value,
21
+ };
22
+ if (config.openai) {
23
+ await Promise.all(config.locales
24
+ .filter((locale) => locale !== sourceLocale)
25
+ .map(async (locale) => {
26
+ const translation = await translateKey({
27
+ context: config.context || "",
28
+ inputLanguage: sourceLocale,
29
+ outputLanguage: locale,
30
+ object: { [key]: value },
31
+ openai: config.openai,
32
+ model: config.model,
33
+ });
34
+ translatedValues[locale] = translation[key];
35
+ }));
36
+ }
37
+ const snapshots = new Map();
38
+ await Promise.all(updatedLocales.flatMap((locale) => targetNamespaces.map(async (namespace) => {
39
+ const sourceSnapshot = locale === sourceLocale
40
+ ? sourceFiles.find((entry) => entry.namespace === namespace)
41
+ ?.translations
42
+ : undefined;
43
+ const existingKeys = sourceSnapshot ??
44
+ (await loadLocalesFile(config.loadPath, locale, namespace, {
45
+ silent: true,
46
+ }));
47
+ snapshots.set(JSON.stringify([locale, namespace]), { ...existingKeys, [key]: translatedValues[locale] });
48
+ })));
49
+ for (const locale of updatedLocales) {
50
+ for (const namespace of targetNamespaces) {
51
+ const snapshot = snapshots.get(JSON.stringify([locale, namespace]));
52
+ if (!snapshot) {
53
+ throw new Error(`Internal error: no staged snapshot for ${locale}/${namespace}.`);
54
+ }
55
+ await writeLocalesFile(config.savePath, locale, namespace, snapshot);
56
+ }
57
+ }
58
+ return {
59
+ key,
60
+ value,
61
+ sourceLocale,
62
+ targetNamespaces,
63
+ updatedLocales,
64
+ pendingLocales,
65
+ };
66
+ };
67
+ //# sourceMappingURL=update-translation-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-translation-key.js","sourceRoot":"","sources":["../../src/lib/update-translation-key.ts"],"names":[],"mappings":"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"}
@@ -85,6 +85,7 @@ export declare const addTranslationKeys: ({ keys, config, }: {
85
85
  results: {
86
86
  key: string;
87
87
  value: string;
88
+ providedLanguage: string;
88
89
  namespace: string;
89
90
  locale: string;
90
91
  }[];
@@ -103,6 +104,7 @@ export declare const addTranslationKey: ({ key, value, language, config, }: {
103
104
  }) => Promise<{
104
105
  key: string;
105
106
  value: string;
107
+ providedLanguage: string;
106
108
  namespace: string;
107
109
  locale: string;
108
110
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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"}
package/dist/lib/utils.js CHANGED
@@ -6,11 +6,12 @@ import { Parser } from "i18next-scanner";
6
6
  import { minimatch } from "minimatch";
7
7
  import prompts from "prompts";
8
8
  import { languages } from "./languges.js";
9
+ import { resolveConfiguredLocale, resolveLocaleTemplatePath, } from "./locale-storage.js";
10
+ import { parseTranslationResponse, TranslationProviderUnavailableError, TranslationResponseError, } from "./translation-response.js";
9
11
  export const loadConfig = async ({ configPath = "i18n-magic.js", } = {}) => {
10
12
  const filePath = path.join(process.cwd(), configPath);
11
13
  if (!fs.existsSync(filePath)) {
12
- console.error("Config file does not exist:", filePath);
13
- process.exit(1);
14
+ throw new Error(`Config file does not exist: ${filePath}`);
14
15
  }
15
16
  try {
16
17
  // Use dynamic import for ESM compatibility
@@ -23,18 +24,10 @@ export const loadConfig = async ({ configPath = "i18n-magic.js", } = {}) => {
23
24
  const errorMessage = error instanceof Error ? error.message : String(error);
24
25
  const isModuleNotFound = errorMessage.includes("MODULE_NOT_FOUND") ||
25
26
  errorMessage.includes("Cannot find module");
26
- console.error("Error while loading config:", error);
27
- if (isModuleNotFound) {
28
- console.error("\n💡 Tip: This error usually occurs when:");
29
- console.error(" 1. Your config file imports dependencies that aren't installed");
30
- console.error(" 2. Dependencies are installed but have missing peer dependencies");
31
- console.error(" 3. You're using a custom storage solution (e.g., AWS S3) without proper dependencies");
32
- console.error("\n To fix:");
33
- console.error(" - Check if your config file imports any external packages");
34
- console.error(" - Ensure all dependencies are properly installed: pnpm install");
35
- console.error(" - If using AWS SDK, ensure all @smithy/* peer dependencies are installed");
36
- }
37
- process.exit(1);
27
+ const hint = isModuleNotFound
28
+ ? " Check the config imports and installed dependencies."
29
+ : "";
30
+ throw new Error(`Error while loading config: ${errorMessage}.${hint}`);
38
31
  }
39
32
  };
40
33
  export function removeDuplicatesFromArray(arr) {
@@ -63,6 +56,10 @@ const extractKeysWithRegex = (content) => {
63
56
  export const translateKey = async ({ inputLanguage, context, object, openai, outputLanguage, model, onProgress, }) => {
64
57
  // Split object into chunks of 100 keys
65
58
  const entries = Object.entries(object);
59
+ if (entries.length === 0)
60
+ return {};
61
+ if (!openai)
62
+ throw new TranslationProviderUnavailableError();
66
63
  const chunks = [];
67
64
  for (let i = 0; i < entries.length; i += 100) {
68
65
  chunks.push(entries.slice(i, i + 100));
@@ -77,29 +74,47 @@ export const translateKey = async ({ inputLanguage, context, object, openai, out
77
74
  const totalKeys = entries.length;
78
75
  for (const chunk of chunks) {
79
76
  const chunkObject = Object.fromEntries(chunk);
80
- const completion = await openai.chat.completions.create({
81
- model,
82
- messages: [
83
- {
84
- content: `You are a bot that translates the values of a locales JSON. ${context
85
- ? `The user provided some additional context or guidelines about what to fill in the blanks: "${context}". `
86
- : ""}The user provides you a JSON with a field named "inputLanguage", which defines the language the values of the JSON are defined in. It also has a field named "outputLanguage", which defines the language you should translate the values to. The last field is named "data", which includes the object with the values to translate. The keys of the values should never be changed. You output only a JSON, which has the same keys as the input, but with translated values. I give you an example input: {"inputLanguage": "English", outputLanguage: "German", "keys": {"hello": "Hello", "world": "World"}}. The output should be {"hello": "Hallo", "world": "Welt"}.`,
87
- role: "system",
77
+ const requestedKeys = chunk.map(([key]) => key);
78
+ let translatedChunk;
79
+ for (let attempt = 0; attempt < 2; attempt++) {
80
+ const correctivePrompt = attempt === 1
81
+ ? ` The previous response was invalid. Return exactly these keys with string values and no additional keys: ${JSON.stringify(requestedKeys)}.`
82
+ : "";
83
+ const completion = await openai.chat.completions.create({
84
+ model,
85
+ messages: [
86
+ {
87
+ content: `You are a bot that translates the values of a locales JSON. ${context
88
+ ? `The user provided some additional context or guidelines about what to fill in the blanks: "${context}". `
89
+ : ""}The user provides you a JSON with a field named "inputLanguage", which defines the language the values of the JSON are defined in. It also has a field named "outputLanguage", which defines the language you should translate the values to. The last field is named "data", which includes the object with the values to translate. The keys of the values should never be changed. You output only a JSON, which has the same keys as the input, but with translated values. I give you an example input: {"inputLanguage": "English", outputLanguage: "German", "keys": {"hello": "Hello", "world": "World"}}. The output should be {"hello": "Hallo", "world": "Welt"}.${correctivePrompt}`,
90
+ role: "system",
91
+ },
92
+ {
93
+ content: JSON.stringify({
94
+ inputLanguage: input,
95
+ outputLanguage: output,
96
+ data: chunkObject,
97
+ }),
98
+ role: "user",
99
+ },
100
+ ],
101
+ response_format: {
102
+ type: "json_object",
88
103
  },
89
- {
90
- content: JSON.stringify({
91
- inputLanguage: input,
92
- outputLanguage: output,
93
- data: chunkObject,
94
- }),
95
- role: "user",
96
- },
97
- ],
98
- response_format: {
99
- type: "json_object",
100
- },
101
- });
102
- const translatedChunk = JSON.parse(completion.choices[0].message.content);
104
+ });
105
+ try {
106
+ translatedChunk = parseTranslationResponse(completion.choices[0]?.message.content ?? null, requestedKeys);
107
+ break;
108
+ }
109
+ catch (error) {
110
+ if (!(error instanceof TranslationResponseError) || attempt === 1) {
111
+ throw error;
112
+ }
113
+ }
114
+ }
115
+ if (!translatedChunk) {
116
+ throw new TranslationResponseError("Translation provider did not return a valid response.");
117
+ }
103
118
  // Merge translated chunk with result
104
119
  result = { ...result, ...translatedChunk };
105
120
  // Report progress
@@ -115,9 +130,7 @@ export const translateKey = async ({ inputLanguage, context, object, openai, out
115
130
  export const loadLocalesFile = async (loadPath, locale, namespace, options) => {
116
131
  const silent = options?.silent ?? false;
117
132
  if (typeof loadPath === "string") {
118
- const resolvedPath = loadPath
119
- .replace("{{lng}}", locale)
120
- .replace("{{ns}}", namespace);
133
+ const resolvedPath = resolveLocaleTemplatePath(loadPath, locale, namespace);
121
134
  // Check if file exists, return empty object if it doesn't
122
135
  if (!fs.existsSync(resolvedPath)) {
123
136
  if (!silent) {
@@ -138,9 +151,7 @@ export const loadLocalesFile = async (loadPath, locale, namespace, options) => {
138
151
  };
139
152
  export const writeLocalesFile = async (savePath, locale, namespace, data) => {
140
153
  if (typeof savePath === "string") {
141
- const resolvedSavePath = savePath
142
- .replace("{{lng}}", locale)
143
- .replace("{{ns}}", namespace);
154
+ const resolvedSavePath = resolveLocaleTemplatePath(savePath, locale, namespace);
144
155
  // Ensure directory exists
145
156
  const dir = path.dirname(resolvedSavePath);
146
157
  if (!fs.existsSync(dir)) {
@@ -592,6 +603,11 @@ export const addTranslationKeys = async ({ keys, config, }) => {
592
603
  },
593
604
  };
594
605
  }
606
+ const validatedKeys = keys.map(({ key, value, language }) => ({
607
+ key,
608
+ value,
609
+ language: resolveConfiguredLocale(config, language),
610
+ }));
595
611
  const log = console.log;
596
612
  const namespaceResolutionDebug = process.env.DEBUG_NAMESPACE_RESOLUTION === "1" ||
597
613
  process.env.DEBUG_NAMESPACE_RESOLUTION === "true";
@@ -632,7 +648,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
632
648
  defaultLocaleKeysByNamespace.set(namespace, {});
633
649
  }
634
650
  }));
635
- const preparedKeys = keys.map(({ key, value, language = "en" }) => {
651
+ const preparedKeys = validatedKeys.map(({ key, value, language }) => {
636
652
  const splitKey = key.split(":");
637
653
  const hasNamespacedKey = splitKey.length > 1 && namespaces.includes(splitKey[0]);
638
654
  const normalizedKey = hasNamespacedKey ? splitKey.slice(1).join(":") : key;
@@ -709,6 +725,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
709
725
  const fileIOStartTime = performance.now();
710
726
  const localeFiles = new Map();
711
727
  const originalKeyCounts = new Map();
728
+ const changedFiles = new Set();
712
729
  const loadErrors = [];
713
730
  // Load files for all locales × all affected namespaces in parallel (read-only)
714
731
  const localeLoadPromises = [];
@@ -746,6 +763,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
746
763
  existingKeys[key] = value;
747
764
  }
748
765
  localeFiles.set(fileKey, existingKeys);
766
+ changedFiles.add(fileKey);
749
767
  }
750
768
  // Step 7: Batch translate if OpenAI is configured
751
769
  const translationStartTime = performance.now();
@@ -776,12 +794,8 @@ export const addTranslationKeys = async ({ keys, config, }) => {
776
794
  object: keysToTranslate,
777
795
  openai,
778
796
  model,
779
- })
780
- .then((translated) => {
797
+ }).then((translated) => {
781
798
  translationCache.set(`${inputLanguage}:${targetLocale}`, translated);
782
- })
783
- .catch((error) => {
784
- log(`⚠️ Failed to translate ${keyValues.length} key(s) from ${inputLanguage} to ${targetLocale}: ${error instanceof Error ? error.message : "Unknown error"}`);
785
799
  }));
786
800
  }
787
801
  }
@@ -794,7 +808,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
794
808
  if (!translated)
795
809
  continue;
796
810
  for (const { key, namespaces } of keyValues) {
797
- if (translated[key]) {
811
+ if (Object.hasOwn(translated, key)) {
798
812
  for (const namespace of namespaces) {
799
813
  const fileKey = `${targetLocale}:${namespace}`;
800
814
  // File MUST already be loaded from step 5 - if not, something went wrong
@@ -804,6 +818,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
804
818
  }
805
819
  // Merge translated key with existing keys (don't overwrite the whole file)
806
820
  localeFiles.get(fileKey)[key] = translated[key];
821
+ changedFiles.add(fileKey);
807
822
  }
808
823
  }
809
824
  }
@@ -828,6 +843,8 @@ export const addTranslationKeys = async ({ keys, config, }) => {
828
843
  }
829
844
  // All validations passed, now write files sequentially to avoid race conditions
830
845
  for (const [fileKey, keys] of localeFiles) {
846
+ if (!changedFiles.has(fileKey))
847
+ continue;
831
848
  const [locale, namespace] = fileKey.split(":");
832
849
  await writeLocalesFile(savePath, locale, namespace, keys);
833
850
  }
@@ -843,7 +860,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
843
860
  config.locales.forEach((locale) => {
844
861
  if (locale !== language) {
845
862
  const translated = translationCache.get(`${language}:${locale}`);
846
- if (translated?.[key]) {
863
+ if (translated && Object.hasOwn(translated, key)) {
847
864
  savedLocales.add(locale);
848
865
  }
849
866
  }
@@ -852,6 +869,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
852
869
  return {
853
870
  key,
854
871
  value,
872
+ providedLanguage: language,
855
873
  namespace: resolvedNamespaces.join(", "),
856
874
  locale: Array.from(savedLocales).sort().join(", "),
857
875
  };
@@ -866,7 +884,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
866
884
  },
867
885
  };
868
886
  };
869
- export const addTranslationKey = async ({ key, value, language = "en", config, }) => {
887
+ export const addTranslationKey = async ({ key, value, language, config, }) => {
870
888
  const startTime = performance.now();
871
889
  const result = await addTranslationKeys({
872
890
  keys: [{ key, value, language }],