@ayinza_dev/i18n-config 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +1 -0
- package/dist/formatters.d.ts +7 -0
- package/dist/formatters.js +17 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/parser-hooks.d.ts +21 -0
- package/dist/parser-hooks.js +70 -22
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -57,6 +57,7 @@ const mergeFormatters = (base, override) => {
|
|
|
57
57
|
...override?.date,
|
|
58
58
|
},
|
|
59
59
|
fallbackLocale: override?.fallbackLocale ?? base?.fallbackLocale,
|
|
60
|
+
numberingSystem: override?.numberingSystem ?? base?.numberingSystem,
|
|
60
61
|
};
|
|
61
62
|
};
|
|
62
63
|
function mergeConfig(base, override) {
|
package/dist/formatters.d.ts
CHANGED
|
@@ -4,9 +4,16 @@ export declare class I18nFormatters {
|
|
|
4
4
|
private numberConfig;
|
|
5
5
|
private dateConfig;
|
|
6
6
|
private fallbackLocale;
|
|
7
|
+
private numberingSystem;
|
|
7
8
|
private normalizeLanguage;
|
|
8
9
|
constructor(config?: FormattersConfig);
|
|
9
10
|
private getLocale;
|
|
11
|
+
/**
|
|
12
|
+
* Force a numbering system across every Intl formatter (incl. RelativeTime,
|
|
13
|
+
* which has no `numberingSystem` option) by appending the Unicode `-u-nu-`
|
|
14
|
+
* locale extension. No-op when disabled or already present.
|
|
15
|
+
*/
|
|
16
|
+
private applyNumberingSystem;
|
|
10
17
|
/**
|
|
11
18
|
* Format with locale-specific formatting
|
|
12
19
|
*/
|
package/dist/formatters.js
CHANGED
|
@@ -21,6 +21,10 @@ export class I18nFormatters {
|
|
|
21
21
|
constructor(config = {}) {
|
|
22
22
|
this.fallbackLocale =
|
|
23
23
|
config.fallbackLocale || defaultLocaleMapping.en || "en-US";
|
|
24
|
+
// Default to Western (latn) digits so localized output keeps native words
|
|
25
|
+
// (month/weekday/currency labels) and RTL but renders 0-9. `??` (not `||`)
|
|
26
|
+
// so an explicit "" opts back into each locale's native numbering.
|
|
27
|
+
this.numberingSystem = config.numberingSystem ?? "latn";
|
|
24
28
|
this.currencyConfig = {
|
|
25
29
|
defaultCurrency: config.currency?.defaultCurrency || "USD",
|
|
26
30
|
localeMapping: {
|
|
@@ -48,7 +52,19 @@ export class I18nFormatters {
|
|
|
48
52
|
}
|
|
49
53
|
getLocale(language, mapping) {
|
|
50
54
|
const { full, base } = this.normalizeLanguage(language);
|
|
51
|
-
|
|
55
|
+
const locale = mapping[full] || mapping[base] || this.fallbackLocale;
|
|
56
|
+
return this.applyNumberingSystem(locale);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Force a numbering system across every Intl formatter (incl. RelativeTime,
|
|
60
|
+
* which has no `numberingSystem` option) by appending the Unicode `-u-nu-`
|
|
61
|
+
* locale extension. No-op when disabled or already present.
|
|
62
|
+
*/
|
|
63
|
+
applyNumberingSystem(locale) {
|
|
64
|
+
if (!this.numberingSystem || locale.includes("-u-nu-")) {
|
|
65
|
+
return locale;
|
|
66
|
+
}
|
|
67
|
+
return `${locale}-u-nu-${this.numberingSystem}`;
|
|
52
68
|
}
|
|
53
69
|
/**
|
|
54
70
|
* Format with locale-specific formatting
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { I18nConfig, I18nInitOptions, FormattersConfig, LocaleMapping, Pars
|
|
|
3
3
|
export { I18nFormatters } from "./formatters.js";
|
|
4
4
|
export { useFormatting, useI18n } from "./hooks.js";
|
|
5
5
|
export { createI18nextParserConfig } from "./parser-config.js";
|
|
6
|
-
export { createTranslationSnapshot, collectNewTranslationKeys, handleNewTranslationKeys, } from "./parser-hooks.js";
|
|
6
|
+
export { createTranslationSnapshot, collectNewTranslationKeys, handleNewTranslationKeys, publishTranslationOverrides, } from "./parser-hooks.js";
|
|
7
|
+
export type { PublishTranslationOverridesOptions } from "./parser-hooks.js";
|
|
7
8
|
export { useTranslation, Trans, Translation } from "react-i18next";
|
|
8
9
|
export { type TFunction } from "i18next";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,6 @@ export { initializeI18n, getI18nInstance, getFormatters, defaultConfig, createI1
|
|
|
2
2
|
export { I18nFormatters } from "./formatters.js";
|
|
3
3
|
export { useFormatting, useI18n } from "./hooks.js";
|
|
4
4
|
export { createI18nextParserConfig } from "./parser-config.js";
|
|
5
|
-
export { createTranslationSnapshot, collectNewTranslationKeys, handleNewTranslationKeys, } from "./parser-hooks.js";
|
|
5
|
+
export { createTranslationSnapshot, collectNewTranslationKeys, handleNewTranslationKeys, publishTranslationOverrides, } from "./parser-hooks.js";
|
|
6
6
|
// Re-export commonly used utilities from react-i18next
|
|
7
7
|
export { useTranslation, Trans, Translation } from "react-i18next";
|
package/dist/parser-hooks.d.ts
CHANGED
|
@@ -21,3 +21,24 @@ export interface HandleNewTranslationKeysResult {
|
|
|
21
21
|
export declare const createTranslationSnapshot: (options: CreateTranslationSnapshotOptions) => TranslationSnapshot;
|
|
22
22
|
export declare const collectNewTranslationKeys: (options: CollectNewTranslationKeysOptions) => NewKeyPayload[];
|
|
23
23
|
export declare const handleNewTranslationKeys: (options: HandleNewTranslationKeysOptions) => Promise<HandleNewTranslationKeysResult>;
|
|
24
|
+
export interface PublishTranslationOverridesOptions {
|
|
25
|
+
/** Target locale whose values are being corrected, e.g. `ar-SA`. */
|
|
26
|
+
locale: string;
|
|
27
|
+
/** Flat `key -> corrected value` map. Only keys present are overridden. */
|
|
28
|
+
translations: Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Push settings. `pushUrl` is the override collection endpoint (e.g.
|
|
31
|
+
* `${base}/l10n/admin/translations`); the locale is appended per request.
|
|
32
|
+
*/
|
|
33
|
+
pushConfig: ParserPushConfig;
|
|
34
|
+
requestInit?: RequestInit;
|
|
35
|
+
logger?: Pick<Console, "log" | "warn" | "error">;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Publish curated translation overrides for a locale to the localization
|
|
39
|
+
* service's upsert endpoint (`PUT /admin/translations/{locale}`). Any Ayinza
|
|
40
|
+
* product uses this to correct its own catalog (e.g. replacing garbled machine
|
|
41
|
+
* translations); the service marks the rows human-authoritative so the MT
|
|
42
|
+
* sweep never re-manufactures them.
|
|
43
|
+
*/
|
|
44
|
+
export declare const publishTranslationOverrides: (options: PublishTranslationOverridesOptions) => Promise<HandleNewTranslationKeysResult>;
|
package/dist/parser-hooks.js
CHANGED
|
@@ -76,31 +76,22 @@ const headersToRecord = (headers) => {
|
|
|
76
76
|
}
|
|
77
77
|
return { ...headers };
|
|
78
78
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
const translations = {};
|
|
87
|
-
for (const keyEntry of newKeys) {
|
|
88
|
-
translations[keyEntry.key] = keyEntry.defaultValue;
|
|
89
|
-
}
|
|
79
|
+
/**
|
|
80
|
+
* Send a `{ data: { translations, category? } }` catalog payload to the
|
|
81
|
+
* localization-service. Shared by the source-key push (POST /translate/send)
|
|
82
|
+
* and the override publish (PUT /admin/translations/{locale}).
|
|
83
|
+
*/
|
|
84
|
+
const sendCatalogPayload = async (options) => {
|
|
85
|
+
const { url, method, translations, pushConfig, requestInit } = options;
|
|
90
86
|
const payload = {
|
|
91
87
|
data: {
|
|
92
88
|
translations,
|
|
93
|
-
// Tag
|
|
89
|
+
// Tag the payload with the producer's category when configured so
|
|
94
90
|
// consumers can fetch a scoped catalog. Omitted entirely when unset to
|
|
95
91
|
// preserve the un-categorised (shared) default.
|
|
96
92
|
...(pushConfig.category ? { category: pushConfig.category } : {}),
|
|
97
93
|
},
|
|
98
94
|
};
|
|
99
|
-
if (dryRun) {
|
|
100
|
-
logger.log(`💡 Dry run for ${pushConfig.portalName}:\n` +
|
|
101
|
-
newKeys.map((key) => ` • ${key.namespace}:${key.key}`).join("\n"));
|
|
102
|
-
return { pushed: 0, dryRun: true };
|
|
103
|
-
}
|
|
104
95
|
const fetchImpl = pushConfig.fetchImpl ?? globalThis.fetch;
|
|
105
96
|
if (!fetchImpl) {
|
|
106
97
|
throw new Error("No fetch implementation available. Provide pushConfig.fetchImpl in non-Node 18 environments.");
|
|
@@ -113,17 +104,74 @@ export const handleNewTranslationKeys = async (options) => {
|
|
|
113
104
|
headerBag.Authorization = `Bearer ${pushConfig.authorizationToken}`;
|
|
114
105
|
}
|
|
115
106
|
Object.assign(headerBag, headersToRecord(requestInit?.headers));
|
|
116
|
-
const
|
|
107
|
+
const response = await fetchImpl(url, {
|
|
117
108
|
...requestInit,
|
|
118
|
-
method
|
|
109
|
+
method,
|
|
119
110
|
body: JSON.stringify(payload),
|
|
120
111
|
headers: headerBag,
|
|
121
|
-
};
|
|
122
|
-
const response = await fetchImpl(pushConfig.pushUrl, init);
|
|
112
|
+
});
|
|
123
113
|
if (!response.ok) {
|
|
124
114
|
const errorBody = await response.text().catch(() => "");
|
|
125
|
-
throw new Error(`Failed to
|
|
115
|
+
throw new Error(`Failed to ${method} translations (${response.status}): ${errorBody}`);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
export const handleNewTranslationKeys = async (options) => {
|
|
119
|
+
const { newKeys, pushConfig, requestInit, logger = console } = options;
|
|
120
|
+
const dryRun = pushConfig.dryRun || !pushConfig.pushUrl;
|
|
121
|
+
if (!newKeys.length) {
|
|
122
|
+
logger.log(`🗒️ No new translation keys for ${pushConfig.portalName}.`);
|
|
123
|
+
return { pushed: 0, dryRun };
|
|
124
|
+
}
|
|
125
|
+
const translations = {};
|
|
126
|
+
for (const keyEntry of newKeys) {
|
|
127
|
+
translations[keyEntry.key] = keyEntry.defaultValue;
|
|
126
128
|
}
|
|
129
|
+
if (dryRun) {
|
|
130
|
+
logger.log(`💡 Dry run for ${pushConfig.portalName}:\n` +
|
|
131
|
+
newKeys.map((key) => ` • ${key.namespace}:${key.key}`).join("\n"));
|
|
132
|
+
return { pushed: 0, dryRun: true };
|
|
133
|
+
}
|
|
134
|
+
await sendCatalogPayload({
|
|
135
|
+
url: pushConfig.pushUrl,
|
|
136
|
+
method: "POST",
|
|
137
|
+
translations,
|
|
138
|
+
pushConfig,
|
|
139
|
+
requestInit,
|
|
140
|
+
});
|
|
127
141
|
logger.log(`✅ Pushed ${newKeys.length} keys for ${pushConfig.portalName}.`);
|
|
128
142
|
return { pushed: newKeys.length, dryRun: false };
|
|
129
143
|
};
|
|
144
|
+
/**
|
|
145
|
+
* Publish curated translation overrides for a locale to the localization
|
|
146
|
+
* service's upsert endpoint (`PUT /admin/translations/{locale}`). Any Ayinza
|
|
147
|
+
* product uses this to correct its own catalog (e.g. replacing garbled machine
|
|
148
|
+
* translations); the service marks the rows human-authoritative so the MT
|
|
149
|
+
* sweep never re-manufactures them.
|
|
150
|
+
*/
|
|
151
|
+
export const publishTranslationOverrides = async (options) => {
|
|
152
|
+
const { locale, translations, pushConfig, requestInit, logger = console } = options;
|
|
153
|
+
const count = Object.keys(translations).length;
|
|
154
|
+
const dryRun = pushConfig.dryRun || !pushConfig.pushUrl;
|
|
155
|
+
if (!count) {
|
|
156
|
+
logger.log(`🗒️ No translation overrides for ${pushConfig.portalName}.`);
|
|
157
|
+
return { pushed: 0, dryRun };
|
|
158
|
+
}
|
|
159
|
+
if (dryRun) {
|
|
160
|
+
logger.log(`💡 Dry run for ${pushConfig.portalName} (${locale}):\n` +
|
|
161
|
+
Object.keys(translations)
|
|
162
|
+
.map((key) => ` • ${key}`)
|
|
163
|
+
.join("\n"));
|
|
164
|
+
return { pushed: 0, dryRun: true };
|
|
165
|
+
}
|
|
166
|
+
const base = pushConfig.pushUrl.replace(/\/+$/, "");
|
|
167
|
+
const url = `${base}/${encodeURIComponent(locale)}`;
|
|
168
|
+
await sendCatalogPayload({
|
|
169
|
+
url,
|
|
170
|
+
method: "PUT",
|
|
171
|
+
translations,
|
|
172
|
+
pushConfig,
|
|
173
|
+
requestInit,
|
|
174
|
+
});
|
|
175
|
+
logger.log(`✅ Published ${count} ${locale} overrides for ${pushConfig.portalName}.`);
|
|
176
|
+
return { pushed: count, dryRun: false };
|
|
177
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -21,6 +21,15 @@ export interface FormattersConfig {
|
|
|
21
21
|
date?: DateConfig;
|
|
22
22
|
/** Locale to fall back to when a language code is missing from mappings. */
|
|
23
23
|
fallbackLocale?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Unicode numbering system applied to all number/date/currency/percent/
|
|
26
|
+
* relative-time output via the locale's `-u-nu-` extension. Defaults to
|
|
27
|
+
* `"latn"` (Western 0-9 digits) so locales like `ar-SA` keep Arabic words
|
|
28
|
+
* (month/weekday names, currency labels) and RTL while rendering Western
|
|
29
|
+
* digits — the common convention for finance/tax UIs. Set to `"arab"` for
|
|
30
|
+
* Eastern Arabic numerals, or `""` to use each locale's native digits.
|
|
31
|
+
*/
|
|
32
|
+
numberingSystem?: string;
|
|
24
33
|
}
|
|
25
34
|
export interface ReactConfig {
|
|
26
35
|
useSuspense?: boolean;
|