@prosopo/locale 3.6.2 → 3.8.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/.turbo/turbo-build$colon$cjs.log +3 -6
- package/.turbo/turbo-build$colon$tsc.log +6 -9
- package/.turbo/turbo-build.log +5 -11
- package/CHANGELOG.md +43 -0
- package/LICENSE +201 -0
- package/dist/cjs/locales/en/translation.cjs +24 -2
- package/dist/locales/ar/translation.json +24 -2
- package/dist/locales/az/translation.json +24 -2
- package/dist/locales/cs/translation.json +24 -2
- package/dist/locales/de/translation.json +24 -2
- package/dist/locales/el/translation.json +24 -2
- package/dist/locales/en/translation.js +24 -2
- package/dist/locales/en/translation.json +24 -2
- package/dist/locales/es/translation.json +24 -2
- package/dist/locales/fi/translation.json +24 -2
- package/dist/locales/fr/translation.json +24 -2
- package/dist/locales/hi/translation.json +24 -2
- package/dist/locales/hu/translation.json +24 -2
- package/dist/locales/id/translation.json +24 -2
- package/dist/locales/it/translation.json +24 -2
- package/dist/locales/ja/translation.json +24 -2
- package/dist/locales/jv/translation.json +24 -2
- package/dist/locales/ko/translation.json +24 -2
- package/dist/locales/ml/translation.json +24 -2
- package/dist/locales/ms/translation.json +24 -2
- package/dist/locales/nl/translation.json +24 -2
- package/dist/locales/no/translation.json +24 -2
- package/dist/locales/pl/translation.json +24 -2
- package/dist/locales/pt/translation.json +24 -2
- package/dist/locales/pt-BR/translation.json +24 -2
- package/dist/locales/ro/translation.json +24 -2
- package/dist/locales/ru/translation.json +24 -2
- package/dist/locales/sr/translation.json +24 -2
- package/dist/locales/sv/translation.json +24 -2
- package/dist/locales/th/translation.json +24 -2
- package/dist/locales/tr/translation.json +24 -2
- package/dist/locales/uk/translation.json +24 -2
- package/dist/locales/vi/translation.json +24 -2
- package/dist/locales/zh-CN/translation.json +24 -2
- package/package.json +65 -66
- package/src/direction.ts +0 -36
- package/src/errorMessage.ts +0 -37
- package/src/i18SharedOptions.ts +0 -29
- package/src/i18nBackend.ts +0 -69
- package/src/i18nFrontend.ts +0 -267
- package/src/i18nMiddleware.ts +0 -35
- package/src/index.ts +0 -33
- package/src/loadI18next.ts +0 -136
- package/src/locales/ar/translation.json +0 -493
- package/src/locales/az/translation.json +0 -493
- package/src/locales/cs/translation.json +0 -493
- package/src/locales/de/translation.json +0 -493
- package/src/locales/el/translation.json +0 -493
- package/src/locales/en/translation.json +0 -493
- package/src/locales/es/translation.json +0 -493
- package/src/locales/fi/translation.json +0 -493
- package/src/locales/fr/translation.json +0 -493
- package/src/locales/hi/translation.json +0 -493
- package/src/locales/hu/translation.json +0 -493
- package/src/locales/id/translation.json +0 -493
- package/src/locales/it/translation.json +0 -493
- package/src/locales/ja/translation.json +0 -493
- package/src/locales/jv/translation.json +0 -493
- package/src/locales/ko/translation.json +0 -493
- package/src/locales/ml/translation.json +0 -493
- package/src/locales/ms/translation.json +0 -493
- package/src/locales/nl/translation.json +0 -493
- package/src/locales/no/translation.json +0 -493
- package/src/locales/pl/translation.json +0 -493
- package/src/locales/pt/translation.json +0 -493
- package/src/locales/pt-BR/translation.json +0 -493
- package/src/locales/ro/translation.json +0 -493
- package/src/locales/ru/translation.json +0 -493
- package/src/locales/sr/translation.json +0 -493
- package/src/locales/sv/translation.json +0 -493
- package/src/locales/th/translation.json +0 -493
- package/src/locales/tr/translation.json +0 -493
- package/src/locales/uk/translation.json +0 -493
- package/src/locales/vi/translation.json +0 -493
- package/src/locales/zh-CN/translation.json +0 -493
- package/src/process.ts +0 -29
- package/src/tests/direction.unit.test.ts +0 -52
- package/src/tests/errorMessage.unit.test.ts +0 -84
- package/src/tests/i18SharedOptions.unit.test.ts +0 -139
- package/src/tests/i18nBackend.unit.test.ts +0 -241
- package/src/tests/i18nFrontend.unit.test.ts +0 -404
- package/src/tests/i18nMiddleware.unit.test.ts +0 -159
- package/src/tests/loadI18next.unit.test.ts +0 -443
- package/src/tests/locale.test-d.ts +0 -184
- package/src/tests/localePlaceholders.unit.test.ts +0 -81
- package/src/tests/locales.unit.test.ts +0 -79
- package/src/tests/process.unit.test.ts +0 -33
- package/src/tests/thrownErrorKeys.unit.test.ts +0 -91
- package/src/tests/translationKey.unit.test.ts +0 -155
- package/src/tests/translations.unit.test.ts +0 -97
- package/src/tests/translator.unit.test.ts +0 -213
- package/src/tests/util.unit.test.ts +0 -102
- package/src/translationKey.ts +0 -73
- package/src/translations.ts +0 -55
- package/src/translator.ts +0 -69
- package/src/types.ts +0 -47
- package/src/util.ts +0 -25
- package/tsconfig.cjs.json +0 -21
- package/tsconfig.json +0 -22
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.types.json +0 -9
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type { i18n } from "i18next";
|
|
16
|
-
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
17
|
-
import type { Translator } from "../translator.js";
|
|
18
|
-
|
|
19
|
-
// The translator is a thin wrapper: it forces the package's own i18next
|
|
20
|
-
// instance in rather than letting a host app's provider decide, and exposes the
|
|
21
|
-
// three events that used to drive a React re-render. i18next itself is mocked
|
|
22
|
-
// so the tests observe the wiring rather than i18next's own behaviour.
|
|
23
|
-
|
|
24
|
-
interface FakeI18n {
|
|
25
|
-
readonly instance: i18n;
|
|
26
|
-
readonly handlers: Map<string, Set<() => void>>;
|
|
27
|
-
isInitialized: boolean;
|
|
28
|
-
loadedNamespaces: string[];
|
|
29
|
-
translations: Record<string, string>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const fakeI18n = (): FakeI18n => {
|
|
33
|
-
const handlers = new Map<string, Set<() => void>>();
|
|
34
|
-
const fake: FakeI18n = {
|
|
35
|
-
handlers,
|
|
36
|
-
isInitialized: true,
|
|
37
|
-
loadedNamespaces: ["translation"],
|
|
38
|
-
translations: {},
|
|
39
|
-
instance: {
|
|
40
|
-
t: (key: string): string => fake.translations[key] ?? key,
|
|
41
|
-
get isInitialized(): boolean {
|
|
42
|
-
return fake.isInitialized;
|
|
43
|
-
},
|
|
44
|
-
hasLoadedNamespace: (namespace: string): boolean =>
|
|
45
|
-
fake.loadedNamespaces.includes(namespace),
|
|
46
|
-
on: (event: string, listener: () => void): void => {
|
|
47
|
-
const existing = handlers.get(event) ?? new Set<() => void>();
|
|
48
|
-
existing.add(listener);
|
|
49
|
-
handlers.set(event, existing);
|
|
50
|
-
},
|
|
51
|
-
off: (event: string, listener: () => void): void => {
|
|
52
|
-
handlers.get(event)?.delete(listener);
|
|
53
|
-
},
|
|
54
|
-
} as unknown as i18n,
|
|
55
|
-
};
|
|
56
|
-
return fake;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const emit = (fake: FakeI18n, event: string): void => {
|
|
60
|
-
for (const listener of fake.handlers.get(event) ?? []) {
|
|
61
|
-
listener();
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const load = async (): Promise<{
|
|
66
|
-
createTranslator: (existing?: i18n) => Translator;
|
|
67
|
-
packageInstance: i18n;
|
|
68
|
-
initCount: () => number;
|
|
69
|
-
}> => {
|
|
70
|
-
vi.resetModules();
|
|
71
|
-
|
|
72
|
-
const packageInstance = fakeI18n().instance;
|
|
73
|
-
let initCalls = 0;
|
|
74
|
-
|
|
75
|
-
vi.doMock("../i18nFrontend.js", () => ({
|
|
76
|
-
default: (): i18n => {
|
|
77
|
-
initCalls += 1;
|
|
78
|
-
return packageInstance;
|
|
79
|
-
},
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
const imported = await import("../translator.js");
|
|
83
|
-
return {
|
|
84
|
-
createTranslator: imported.createTranslator,
|
|
85
|
-
packageInstance,
|
|
86
|
-
initCount: (): number => initCalls,
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
afterEach(() => {
|
|
91
|
-
vi.resetModules();
|
|
92
|
-
vi.doUnmock("../i18nFrontend.js");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("which i18next instance the translator talks to", () => {
|
|
96
|
-
test("initialises the package's own instance when given none", async () => {
|
|
97
|
-
// Without this, a host application running its own i18next would capture
|
|
98
|
-
// the widget's translations and render its keys instead.
|
|
99
|
-
const { createTranslator, packageInstance, initCount } = await load();
|
|
100
|
-
|
|
101
|
-
const translator = createTranslator();
|
|
102
|
-
|
|
103
|
-
expect(translator.i18n).toBe(packageInstance);
|
|
104
|
-
expect(initCount()).toBe(1);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("adopts an instance the caller already has", async () => {
|
|
108
|
-
// The bundle loads i18next once and hands the same instance to every
|
|
109
|
-
// widget on the page, so a second init would be wasted work.
|
|
110
|
-
const { createTranslator, initCount } = await load();
|
|
111
|
-
const existing = fakeI18n().instance;
|
|
112
|
-
|
|
113
|
-
const translator = createTranslator(existing);
|
|
114
|
-
|
|
115
|
-
expect(translator.i18n).toBe(existing);
|
|
116
|
-
expect(initCount()).toBe(0);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe("translating", () => {
|
|
121
|
-
test("delegates to the instance", async () => {
|
|
122
|
-
const { createTranslator } = await load();
|
|
123
|
-
const fake = fakeI18n();
|
|
124
|
-
fake.translations = { "WIDGET.SUBMIT": "Submit" };
|
|
125
|
-
|
|
126
|
-
expect(createTranslator(fake.instance).t("WIDGET.SUBMIT")).toBe("Submit");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("reads the instance at call time, not at creation time", async () => {
|
|
130
|
-
// A language switch mutates the instance in place; a translator that
|
|
131
|
-
// cached `t` would keep serving the previous language.
|
|
132
|
-
const { createTranslator } = await load();
|
|
133
|
-
const fake = fakeI18n();
|
|
134
|
-
const translator = createTranslator(fake.instance);
|
|
135
|
-
|
|
136
|
-
fake.translations = { "WIDGET.SUBMIT": "Envoyer" };
|
|
137
|
-
|
|
138
|
-
expect(translator.t("WIDGET.SUBMIT")).toBe("Envoyer");
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe("readiness", () => {
|
|
143
|
-
test("is ready once the instance has loaded the translation namespace", async () => {
|
|
144
|
-
const { createTranslator } = await load();
|
|
145
|
-
const fake = fakeI18n();
|
|
146
|
-
|
|
147
|
-
expect(createTranslator(fake.instance).isReady()).toBe(true);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("is not ready before initialisation", async () => {
|
|
151
|
-
const { createTranslator } = await load();
|
|
152
|
-
const fake = fakeI18n();
|
|
153
|
-
fake.isInitialized = false;
|
|
154
|
-
|
|
155
|
-
expect(createTranslator(fake.instance).isReady()).toBe(false);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("is not ready while the namespace is still loading", async () => {
|
|
159
|
-
// Widgets render an empty label until this flips, rather than flashing a
|
|
160
|
-
// raw translation key at the user.
|
|
161
|
-
const { createTranslator } = await load();
|
|
162
|
-
const fake = fakeI18n();
|
|
163
|
-
fake.loadedNamespaces = [];
|
|
164
|
-
|
|
165
|
-
expect(createTranslator(fake.instance).isReady()).toBe(false);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe("subscribing", () => {
|
|
170
|
-
test.each(["initialized", "loaded", "languageChanged"])(
|
|
171
|
-
"notifies the listener on %s",
|
|
172
|
-
async (event: string) => {
|
|
173
|
-
const { createTranslator } = await load();
|
|
174
|
-
const fake = fakeI18n();
|
|
175
|
-
const listener = vi.fn<() => void>();
|
|
176
|
-
|
|
177
|
-
createTranslator(fake.instance).subscribe(listener);
|
|
178
|
-
emit(fake, event);
|
|
179
|
-
|
|
180
|
-
expect(listener).toHaveBeenCalledTimes(1);
|
|
181
|
-
},
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
test("stops notifying once unsubscribed", async () => {
|
|
185
|
-
// Widgets unsubscribe on destroy; a leaked listener would keep a torn-down
|
|
186
|
-
// widget alive for the lifetime of the page.
|
|
187
|
-
const { createTranslator } = await load();
|
|
188
|
-
const fake = fakeI18n();
|
|
189
|
-
const listener = vi.fn<() => void>();
|
|
190
|
-
|
|
191
|
-
const unsubscribe = createTranslator(fake.instance).subscribe(listener);
|
|
192
|
-
unsubscribe();
|
|
193
|
-
emit(fake, "languageChanged");
|
|
194
|
-
|
|
195
|
-
expect(listener).not.toHaveBeenCalled();
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("keeps independent subscriptions apart", async () => {
|
|
199
|
-
const { createTranslator } = await load();
|
|
200
|
-
const fake = fakeI18n();
|
|
201
|
-
const first = vi.fn<() => void>();
|
|
202
|
-
const second = vi.fn<() => void>();
|
|
203
|
-
const translator = createTranslator(fake.instance);
|
|
204
|
-
|
|
205
|
-
const unsubscribeFirst = translator.subscribe(first);
|
|
206
|
-
translator.subscribe(second);
|
|
207
|
-
unsubscribeFirst();
|
|
208
|
-
emit(fake, "loaded");
|
|
209
|
-
|
|
210
|
-
expect(first).not.toHaveBeenCalled();
|
|
211
|
-
expect(second).toHaveBeenCalledTimes(1);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import { afterEach, describe, expect, test } from "vitest";
|
|
16
|
-
import { isClientSide, isServerSide } from "../util.js";
|
|
17
|
-
|
|
18
|
-
// The detection reads `window` off the global object, so each test installs a
|
|
19
|
-
// fake and the teardown removes it. Reflect.deleteProperty rather than
|
|
20
|
-
// `delete globalThis.window` keeps TypeScript happy about an optional global.
|
|
21
|
-
type FakeWindow = {
|
|
22
|
-
document?: { createElement?: unknown };
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const setWindow = (value: FakeWindow | undefined): void => {
|
|
26
|
-
if (value === undefined) {
|
|
27
|
-
Reflect.deleteProperty(globalThis, "window");
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
Object.defineProperty(globalThis, "window", {
|
|
31
|
-
value,
|
|
32
|
-
configurable: true,
|
|
33
|
-
writable: true,
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
afterEach(() => {
|
|
38
|
-
setWindow(undefined);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe("isClientSide", () => {
|
|
42
|
-
test("is false under plain node, where there is no window at all", () => {
|
|
43
|
-
expect(isClientSide()).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("is true when window exposes document.createElement", () => {
|
|
47
|
-
setWindow({ document: { createElement: (): void => {} } });
|
|
48
|
-
expect(isClientSide()).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// The three guards are checked in order and each one has to be able to
|
|
52
|
-
// veto on its own, otherwise a half-built environment (jsdom teardown, a
|
|
53
|
-
// server-side-rendering shim that stubs `window` but not `document`) would
|
|
54
|
-
// be misread as a browser and the frontend i18n path would be selected on
|
|
55
|
-
// a server.
|
|
56
|
-
test("is false when window exists but has no document", () => {
|
|
57
|
-
setWindow({});
|
|
58
|
-
expect(isClientSide()).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("is false when document exists but has no createElement", () => {
|
|
62
|
-
setWindow({ document: {} });
|
|
63
|
-
expect(isClientSide()).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("is false when createElement is present but undefined", () => {
|
|
67
|
-
setWindow({ document: { createElement: undefined } });
|
|
68
|
-
expect(isClientSide()).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("returns a real boolean, not the truthy function itself", () => {
|
|
72
|
-
const createElement = (): void => {};
|
|
73
|
-
setWindow({ document: { createElement } });
|
|
74
|
-
const result: boolean = isClientSide();
|
|
75
|
-
expect(result).toBe(true);
|
|
76
|
-
expect(typeof result).toBe("boolean");
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe("isServerSide", () => {
|
|
81
|
-
test("is true under plain node", () => {
|
|
82
|
-
expect(isServerSide()).toBe(true);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("is false in a browser-like environment", () => {
|
|
86
|
-
setWindow({ document: { createElement: (): void => {} } });
|
|
87
|
-
expect(isServerSide()).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("is always the exact negation of isClientSide", () => {
|
|
91
|
-
const environments: (FakeWindow | undefined)[] = [
|
|
92
|
-
undefined,
|
|
93
|
-
{},
|
|
94
|
-
{ document: {} },
|
|
95
|
-
{ document: { createElement: (): void => {} } },
|
|
96
|
-
];
|
|
97
|
-
for (const environment of environments) {
|
|
98
|
-
setWindow(environment);
|
|
99
|
-
expect(isServerSide()).toBe(!isClientSide());
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
package/src/translationKey.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import translationEn from "./locales/en/translation.json" with { type: "json" };
|
|
16
|
-
|
|
17
|
-
export type TranslationNode =
|
|
18
|
-
| {
|
|
19
|
-
[key: string]: TranslationNode | string;
|
|
20
|
-
}
|
|
21
|
-
| string;
|
|
22
|
-
|
|
23
|
-
// Exported so the traversal can be exercised directly against shapes the
|
|
24
|
-
// bundled English translation happens not to contain (deep nesting, empty
|
|
25
|
-
// objects, explicitly-undefined values).
|
|
26
|
-
export function getLeafFieldPath(obj: TranslationNode): string[] {
|
|
27
|
-
if (typeof obj === "string") {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return Object.keys(obj).reduce((arr, key) => {
|
|
32
|
-
const value = obj[key];
|
|
33
|
-
if (value === undefined) {
|
|
34
|
-
throw new Error(`Undefined value for key ${key}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// A string value IS the leaf, so the path ends here. Recursing into it
|
|
38
|
-
// returns [] and the `children.map` below then contributes nothing,
|
|
39
|
-
// which is how this function used to return an empty array for every
|
|
40
|
-
// input — leaving `translationKeys` empty. That list is spread into
|
|
41
|
-
// three mongoose `reason` fields (types-database provider.ts), and
|
|
42
|
-
// mongoose registers its enum validator even for an empty list, so
|
|
43
|
-
// every non-null reason failed validation.
|
|
44
|
-
if (typeof value === "string") {
|
|
45
|
-
return arr.concat(key);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const children = getLeafFieldPath(value);
|
|
49
|
-
|
|
50
|
-
return arr.concat(
|
|
51
|
-
children.map((child) => {
|
|
52
|
-
return `${key}.${child}`;
|
|
53
|
-
}),
|
|
54
|
-
);
|
|
55
|
-
}, [] as string[]);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const translationKeys: string[] = getLeafFieldPath(translationEn);
|
|
59
|
-
|
|
60
|
-
type LeafPath<Node> = {
|
|
61
|
-
[Key in keyof Node & string]: Node[Key] extends string
|
|
62
|
-
? Key
|
|
63
|
-
: `${Key}.${LeafPath<Node[Key]>}`;
|
|
64
|
-
}[keyof Node & string];
|
|
65
|
-
|
|
66
|
-
/** A leaf path in the English catalogue, so a mistyped key fails to compile. */
|
|
67
|
-
export type TranslationKey = LeafPath<typeof translationEn>;
|
|
68
|
-
|
|
69
|
-
const translationKeySet: ReadonlySet<string> = new Set(translationKeys);
|
|
70
|
-
|
|
71
|
-
/** For keys that only exist at runtime, e.g. an error message. */
|
|
72
|
-
export const isTranslationKey = (value: string): value is TranslationKey =>
|
|
73
|
-
translationKeySet.has(value);
|
package/src/translations.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
export const Languages = {
|
|
16
|
-
arabic: "ar",
|
|
17
|
-
azerbaijani: "az",
|
|
18
|
-
czech: "cs",
|
|
19
|
-
german: "de",
|
|
20
|
-
greek: "el",
|
|
21
|
-
english: "en",
|
|
22
|
-
spanish: "es",
|
|
23
|
-
finnish: "fi",
|
|
24
|
-
french: "fr",
|
|
25
|
-
hindi: "hi",
|
|
26
|
-
hungarian: "hu",
|
|
27
|
-
indonesian: "id",
|
|
28
|
-
italian: "it",
|
|
29
|
-
japanese: "ja",
|
|
30
|
-
javanese: "jv",
|
|
31
|
-
korean: "ko",
|
|
32
|
-
malayalam: "ml",
|
|
33
|
-
malay: "ms",
|
|
34
|
-
dutch: "nl",
|
|
35
|
-
norwegian: "no",
|
|
36
|
-
polish: "pl",
|
|
37
|
-
portugeseBrazil: "pt-BR",
|
|
38
|
-
portuguese: "pt",
|
|
39
|
-
romanian: "ro",
|
|
40
|
-
russian: "ru",
|
|
41
|
-
serbian: "sr",
|
|
42
|
-
swedish: "sv",
|
|
43
|
-
thai: "th",
|
|
44
|
-
turkish: "tr",
|
|
45
|
-
ukrainian: "uk",
|
|
46
|
-
vietnamese: "vi",
|
|
47
|
-
chinese: "zh-CN",
|
|
48
|
-
} as const;
|
|
49
|
-
|
|
50
|
-
export type Language = (typeof Languages)[keyof typeof Languages];
|
|
51
|
-
|
|
52
|
-
export const LanguageCodes: readonly string[] = Object.values(Languages);
|
|
53
|
-
|
|
54
|
-
export const isLanguage = (value: string): value is Language =>
|
|
55
|
-
LanguageCodes.includes(value);
|
package/src/translator.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import initializeI18n from "./i18nFrontend.js";
|
|
16
|
-
import type { TranslationKey } from "./translationKey.js";
|
|
17
|
-
import type { Ti18n, TranslateOptions } from "./types.js";
|
|
18
|
-
|
|
19
|
-
const NAMESPACE = "translation";
|
|
20
|
-
|
|
21
|
-
export interface Translator {
|
|
22
|
-
/**
|
|
23
|
-
* Translate a key against the current language. `options` carries
|
|
24
|
-
* interpolation values and the `defaultValue` a widget falls back to when
|
|
25
|
-
* its key has not been added to the bundled catalogues yet.
|
|
26
|
-
*/
|
|
27
|
-
t(key: TranslationKey, options?: TranslateOptions): string;
|
|
28
|
-
/**
|
|
29
|
-
* Whether the namespace for the active language has finished loading.
|
|
30
|
-
* Mirrors `ready` from the old react-i18next hook: widgets render an empty
|
|
31
|
-
* label until it flips, rather than flashing a raw translation key.
|
|
32
|
-
*/
|
|
33
|
-
isReady(): boolean;
|
|
34
|
-
/**
|
|
35
|
-
* Notified when the translation output could have changed — initialisation,
|
|
36
|
-
* a namespace load, or a language switch. Returns an unsubscribe function.
|
|
37
|
-
*/
|
|
38
|
-
subscribe(listener: () => void): () => void;
|
|
39
|
-
readonly i18n: Ti18n;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Vanilla replacement for the `useTranslation` hook: the instance's `t` plus
|
|
44
|
-
* the three events that used to trigger a React re-render.
|
|
45
|
-
*/
|
|
46
|
-
export const createTranslator = (existing?: Ti18n): Translator => {
|
|
47
|
-
const i18n: Ti18n = existing ?? initializeI18n();
|
|
48
|
-
|
|
49
|
-
const isReady = (): boolean =>
|
|
50
|
-
i18n.isInitialized && i18n.hasLoadedNamespace(NAMESPACE);
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
t: (key: TranslationKey, options?: TranslateOptions): string =>
|
|
54
|
-
i18n.t(key, options),
|
|
55
|
-
isReady,
|
|
56
|
-
i18n,
|
|
57
|
-
subscribe: (listener: () => void): (() => void) => {
|
|
58
|
-
const events = ["initialized", "loaded", "languageChanged"] as const;
|
|
59
|
-
for (const event of events) {
|
|
60
|
-
i18n.on(event, listener);
|
|
61
|
-
}
|
|
62
|
-
return () => {
|
|
63
|
-
for (const event of events) {
|
|
64
|
-
i18n.off(event, listener);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
};
|
package/src/types.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type { TranslationKey } from "./translationKey.js";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Interpolation values, plus the `defaultValue` a caller falls back to when its
|
|
19
|
-
* key is not in the catalogue. Values are substituted into `{{name}}`
|
|
20
|
-
* placeholders.
|
|
21
|
-
*/
|
|
22
|
-
export interface TranslateOptions {
|
|
23
|
-
defaultValue?: string;
|
|
24
|
-
[value: string]: unknown;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type TranslateFn = (
|
|
28
|
-
key: TranslationKey,
|
|
29
|
-
options?: TranslateOptions,
|
|
30
|
-
) => string;
|
|
31
|
-
|
|
32
|
-
export type I18nEvent = "initialized" | "loaded" | "languageChanged";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The slice of an i18n instance this repository uses. Both implementations
|
|
36
|
-
* satisfy it: the browser one in `i18nFrontend.ts`, and i18next itself on the
|
|
37
|
-
* server, where `i18next-http-middleware` needs the real thing.
|
|
38
|
-
*/
|
|
39
|
-
export interface Ti18n {
|
|
40
|
-
readonly language: string;
|
|
41
|
-
readonly isInitialized: boolean;
|
|
42
|
-
t: TranslateFn;
|
|
43
|
-
changeLanguage(language: string): Promise<unknown>;
|
|
44
|
-
hasLoadedNamespace(namespace: string): boolean;
|
|
45
|
-
on(event: I18nEvent, listener: () => void): void;
|
|
46
|
-
off(event: I18nEvent, listener: () => void): void;
|
|
47
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
export const isClientSide = (): boolean => {
|
|
16
|
-
return !!(
|
|
17
|
-
typeof window !== "undefined" &&
|
|
18
|
-
window.document &&
|
|
19
|
-
window.document.createElement
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const isServerSide = (): boolean => {
|
|
24
|
-
return !isClientSide();
|
|
25
|
-
};
|
package/tsconfig.cjs.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.cjs.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "./src",
|
|
5
|
-
"outDir": "./dist/cjs"
|
|
6
|
-
},
|
|
7
|
-
"include": [
|
|
8
|
-
"./src/**/*.ts",
|
|
9
|
-
"./src/**/*.json",
|
|
10
|
-
"./src/**/*.d.ts",
|
|
11
|
-
"./src/**/*.tsx"
|
|
12
|
-
],
|
|
13
|
-
"references": [
|
|
14
|
-
{
|
|
15
|
-
"path": "../../dev/config/tsconfig.cjs.json"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"path": "../util/tsconfig.cjs.json"
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.esm.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "./src",
|
|
5
|
-
"outDir": "./dist"
|
|
6
|
-
},
|
|
7
|
-
"include": [
|
|
8
|
-
"src",
|
|
9
|
-
"src/**/*.json",
|
|
10
|
-
"src/**/*.ts",
|
|
11
|
-
"src/**/*.tsx",
|
|
12
|
-
"src/**/*.d.ts"
|
|
13
|
-
],
|
|
14
|
-
"references": [
|
|
15
|
-
{
|
|
16
|
-
"path": "../../dev/config/tsconfig.json"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"path": "../util"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|