@parallelworks/i18n 0.0.0-stage → 0.1.1
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/README.md +81 -2
- package/client.d.ts +12 -0
- package/dist/catalogs.d.ts +26 -0
- package/dist/catalogs.d.ts.map +1 -0
- package/dist/catalogs.js +73 -0
- package/dist/catalogs.js.map +1 -0
- package/dist/check.d.ts +22 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +179 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +42 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/react.d.ts +34 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +68 -0
- package/dist/react.js.map +1 -0
- package/dist/vite.d.ts +17 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +70 -0
- package/dist/vite.js.map +1 -0
- package/package.json +77 -3
package/README.md
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @parallelworks/i18n
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Localization for React apps on [use-intl](https://next-intl.dev/docs/environments/core-library): picking the reader's language, a provider that switches it, catalogs loaded only when needed, and a check that keeps every language in step with the default.
|
|
4
|
+
|
|
5
|
+
Related packages: show API errors with `useErrorMessage()` from `@parallelworks/problem/react`, and keep hard-coded English out of components with `@parallelworks/lint`.
|
|
6
|
+
|
|
7
|
+
## Catalogs
|
|
8
|
+
|
|
9
|
+
Messages live in one directory, in either layout:
|
|
10
|
+
|
|
11
|
+
- one file per locale: `locales/en.json`, `locales/es.json`
|
|
12
|
+
- one file per namespace, for large apps: `locales/en/nav.json`, `locales/en/billing.json`. Unrelated features then rarely edit the same file. `parallelworks-i18n index` writes `locales/en/index.ts`, which imports them all, so `typeof import('./locales/en')` types the messages.
|
|
13
|
+
|
|
14
|
+
## Vite
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// vite.config.ts
|
|
18
|
+
import { i18n } from '@parallelworks/i18n/vite'
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
plugins: [
|
|
22
|
+
i18n({
|
|
23
|
+
dir: 'src/i18n/locales',
|
|
24
|
+
defaultLocale: 'en',
|
|
25
|
+
// Optional: namespaces only these features use, loaded where they're shown.
|
|
26
|
+
catalogs: { admin: ['adminUsers', 'adminBilling'] },
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The plugin serves `virtual:i18n`: `locales`, `defaultLocale`, `defaultMessages` (the default locale's common messages, bundled) and `loadMessages(locale, catalog?)`, which loads every other locale and catalog as its own chunk. Add `"@parallelworks/i18n/client"` to `compilerOptions.types` for its types.
|
|
33
|
+
|
|
34
|
+
## React
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// main.tsx
|
|
38
|
+
import { detectLocale } from '@parallelworks/i18n'
|
|
39
|
+
import { LocaleProvider } from '@parallelworks/i18n/react'
|
|
40
|
+
import { defaultLocale, defaultMessages, loadMessages, locales } from 'virtual:i18n'
|
|
41
|
+
|
|
42
|
+
// The server's choice (if it injects one), a stored choice, then the browser's languages.
|
|
43
|
+
const locale = detectLocale(locales, { fallback: defaultLocale, cookie: 'locale' })
|
|
44
|
+
const messages = locale === defaultLocale ? defaultMessages : await loadMessages(locale)
|
|
45
|
+
|
|
46
|
+
root.render(
|
|
47
|
+
<LocaleProvider
|
|
48
|
+
initial={{ locale, messages }}
|
|
49
|
+
load={loadMessages}
|
|
50
|
+
fallbackLocale={defaultLocale}
|
|
51
|
+
onChange={(next) => {
|
|
52
|
+
document.cookie = `locale=${next}; path=/; max-age=31536000`
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<App />
|
|
56
|
+
</LocaleProvider>,
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `useSetLocale()` switches the language. If the person picks again before the first finishes loading, the later choice wins.
|
|
61
|
+
- The provider keeps `<html lang>` current and passes the browser's time zone to use-intl.
|
|
62
|
+
- `<Catalog name='admin' fallback={<Spinner />}>` adds a feature catalog's messages beneath it, loading them for the current language first.
|
|
63
|
+
- `negotiateLocale(preferred, locales, fallback)` matches tags such as `es-MX` to `es`.
|
|
64
|
+
|
|
65
|
+
## Checks
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
parallelworks-i18n check --dir src/i18n/locales # in CI
|
|
69
|
+
parallelworks-i18n add new-messages.json --dir src/i18n/locales
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`check` fails when a locale is missing a key or has one the default doesn't, when a message isn't valid ICU, when a translation uses different arguments or rich-text tags than the default, or when a split layout's `index.ts` is stale. `add` merges messages without changing an existing one, and is safe to run from several processes at once. Its file is one locale's tree, or trees keyed by locale.
|
|
73
|
+
|
|
74
|
+
`@parallelworks/i18n/node` exports the same functions and the catalog reader for an app's own scripts.
|
|
75
|
+
|
|
76
|
+
## Dates and numbers
|
|
77
|
+
|
|
78
|
+
Format for display with use-intl's `useFormatter()`: `format.dateTime`, `format.relativeTime`, `format.number`. It uses `Intl` with the provider's locale and time zone, so nothing else has to follow the language.
|
|
79
|
+
|
|
80
|
+
For parsing, arithmetic and time zones, use [Luxon](https://moment.github.io/luxon/) for now. Not the built-in `Date`, which is mutable and knows only the local and UTC zones.
|
|
81
|
+
|
|
82
|
+
**Switch to [Temporal](https://tc39.es/proposal-temporal/docs/) once it is Baseline.** Temporal is ES2026 and ships in Chrome 144+ and Firefox 139+, but as of October 2026 not yet in stable Safari. When Safari ships it, move parsing and arithmetic from Luxon to Temporal here and in every app, and drop Luxon.
|
package/client.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Types for the module @parallelworks/i18n/vite serves. Reference it from
|
|
2
|
+
// tsconfig ("types": ["@parallelworks/i18n/client"]).
|
|
3
|
+
declare module 'virtual:i18n' {
|
|
4
|
+
import type { AbstractIntlMessages } from 'use-intl'
|
|
5
|
+
|
|
6
|
+
export const locales: readonly string[]
|
|
7
|
+
export const defaultLocale: string
|
|
8
|
+
/** The default locale's common messages, bundled with the app. */
|
|
9
|
+
export const defaultMessages: AbstractIntlMessages
|
|
10
|
+
/** A locale's common messages, or with catalog, one feature's. */
|
|
11
|
+
export function loadMessages(locale: string, catalog?: string): Promise<AbstractIntlMessages>
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A message catalog: nested objects whose leaves are ICU messages. */
|
|
2
|
+
export type Messages = {
|
|
3
|
+
[key: string]: string | Messages;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* The locales in dir. A locale is either one file, <dir>/<locale>.json, or a
|
|
7
|
+
* directory with one file per namespace, <dir>/<locale>/<namespace>.json.
|
|
8
|
+
*/
|
|
9
|
+
export declare function listLocales(dir: string): string[];
|
|
10
|
+
/** The files a locale's messages come from, for watching them. */
|
|
11
|
+
export declare function localeFiles(dir: string, locale: string): string[];
|
|
12
|
+
/** A locale's messages, from either layout; a split locale is keyed by namespace. */
|
|
13
|
+
export declare function readLocale(dir: string, locale: string): Messages;
|
|
14
|
+
/** Whether dir keeps each locale in one file per namespace. */
|
|
15
|
+
export declare function isSplit(dir: string, locale: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Feature catalogs: each lists the namespaces only that feature needs, loaded
|
|
18
|
+
* when it's shown. Everything else is in the common catalog.
|
|
19
|
+
*/
|
|
20
|
+
export type CatalogMap = Record<string, readonly string[]>;
|
|
21
|
+
export declare const COMMON = "common";
|
|
22
|
+
/** The namespaces of messages that belong in catalog. */
|
|
23
|
+
export declare function selectCatalog(messages: Messages, catalog: string, catalogs: CatalogMap): Messages;
|
|
24
|
+
/** Every message, keyed by its dotted path. */
|
|
25
|
+
export declare function flatten(messages: Messages, prefix?: string): Map<string, string>;
|
|
26
|
+
//# sourceMappingURL=catalogs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalogs.d.ts","sourceRoot":"","sources":["../src/catalogs.ts"],"names":[],"mappings":"AAGA,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAA;AAE3D;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQjD;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAQjE;AAED,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAMhE;AAED,+DAA+D;AAC/D,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE5D;AAUD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;AAE1D,eAAO,MAAM,MAAM,WAAW,CAAA;AAE9B,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,QAAQ,CAQjG;AAED,+CAA+C;AAC/C,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAK,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ5E"}
|
package/dist/catalogs.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* The locales in dir. A locale is either one file, <dir>/<locale>.json, or a
|
|
5
|
+
* directory with one file per namespace, <dir>/<locale>/<namespace>.json.
|
|
6
|
+
*/
|
|
7
|
+
export function listLocales(dir) {
|
|
8
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
9
|
+
.flatMap((entry) => {
|
|
10
|
+
if (entry.isFile() && entry.name.endsWith('.json'))
|
|
11
|
+
return [entry.name.slice(0, -5)];
|
|
12
|
+
if (entry.isDirectory())
|
|
13
|
+
return [entry.name];
|
|
14
|
+
return [];
|
|
15
|
+
})
|
|
16
|
+
.sort();
|
|
17
|
+
}
|
|
18
|
+
/** The files a locale's messages come from, for watching them. */
|
|
19
|
+
export function localeFiles(dir, locale) {
|
|
20
|
+
const file = path.join(dir, `${locale}.json`);
|
|
21
|
+
if (existsSync(file))
|
|
22
|
+
return [file];
|
|
23
|
+
const split = path.join(dir, locale);
|
|
24
|
+
return readdirSync(split)
|
|
25
|
+
.filter((name) => name.endsWith('.json'))
|
|
26
|
+
.sort()
|
|
27
|
+
.map((name) => path.join(split, name));
|
|
28
|
+
}
|
|
29
|
+
/** A locale's messages, from either layout; a split locale is keyed by namespace. */
|
|
30
|
+
export function readLocale(dir, locale) {
|
|
31
|
+
const file = path.join(dir, `${locale}.json`);
|
|
32
|
+
if (existsSync(file) && statSync(file).isFile())
|
|
33
|
+
return parse(file);
|
|
34
|
+
return Object.fromEntries(localeFiles(dir, locale).map((f) => [path.basename(f, '.json'), parse(f)]));
|
|
35
|
+
}
|
|
36
|
+
/** Whether dir keeps each locale in one file per namespace. */
|
|
37
|
+
export function isSplit(dir, locale) {
|
|
38
|
+
return !existsSync(path.join(dir, `${locale}.json`));
|
|
39
|
+
}
|
|
40
|
+
function parse(file) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new Error(`${file}: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export const COMMON = 'common';
|
|
49
|
+
/** The namespaces of messages that belong in catalog. */
|
|
50
|
+
export function selectCatalog(messages, catalog, catalogs) {
|
|
51
|
+
if (catalog !== COMMON) {
|
|
52
|
+
const names = catalogs[catalog];
|
|
53
|
+
if (!names)
|
|
54
|
+
throw new Error(`unknown message catalog ${catalog}`);
|
|
55
|
+
return Object.fromEntries(Object.entries(messages).filter(([ns]) => names.includes(ns)));
|
|
56
|
+
}
|
|
57
|
+
const featured = new Set(Object.values(catalogs).flat());
|
|
58
|
+
return Object.fromEntries(Object.entries(messages).filter(([ns]) => !featured.has(ns)));
|
|
59
|
+
}
|
|
60
|
+
/** Every message, keyed by its dotted path. */
|
|
61
|
+
export function flatten(messages, prefix = '') {
|
|
62
|
+
const out = new Map();
|
|
63
|
+
for (const [key, value] of Object.entries(messages)) {
|
|
64
|
+
const at = prefix ? `${prefix}.${key}` : key;
|
|
65
|
+
if (typeof value === 'string')
|
|
66
|
+
out.set(at, value);
|
|
67
|
+
else
|
|
68
|
+
for (const [k, v] of flatten(value, at))
|
|
69
|
+
out.set(k, v);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=catalogs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalogs.js","sourceRoot":"","sources":["../src/catalogs.ts"],"names":[],"mappings":"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"}
|
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Messages } from './catalogs.js';
|
|
2
|
+
/**
|
|
3
|
+
* What's wrong with dir's catalogs, one line each: a key missing from or
|
|
4
|
+
* extra to a locale, a message that isn't valid ICU, a translation that uses
|
|
5
|
+
* different arguments or tags than the default locale's, or a stale index.
|
|
6
|
+
*/
|
|
7
|
+
export declare function check(dir: string, defaultLocale: string): string[];
|
|
8
|
+
/** The sorted argument and tag names of an ICU message, or why it doesn't parse. */
|
|
9
|
+
export declare function argumentsOf(message: string): string[] | string;
|
|
10
|
+
/**
|
|
11
|
+
* For a locale kept as one file per namespace, a TypeScript module importing
|
|
12
|
+
* every namespace, so `typeof import('<dir>/<locale>')` types the messages.
|
|
13
|
+
*/
|
|
14
|
+
export declare function indexSource(dir: string, locale: string): string;
|
|
15
|
+
export declare function writeIndex(dir: string, locale: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Merges additions into dir's catalogs without changing an existing message.
|
|
18
|
+
* additions is one locale's tree for defaultLocale, or trees keyed by locale.
|
|
19
|
+
* Safe to run from several processes at once.
|
|
20
|
+
*/
|
|
21
|
+
export declare function add(dir: string, defaultLocale: string, additions: Messages): void;
|
|
22
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,eAAe,CAAA;AAEtB;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CA6ClE;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,CA4B9D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI/D;AAMD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,GAAG,IAAI,CAyBjF"}
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, rmdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse, TYPE } from '@formatjs/icu-messageformat-parser';
|
|
4
|
+
import { flatten, isSplit, listLocales, localeFiles, readLocale, } from './catalogs.js';
|
|
5
|
+
/**
|
|
6
|
+
* What's wrong with dir's catalogs, one line each: a key missing from or
|
|
7
|
+
* extra to a locale, a message that isn't valid ICU, a translation that uses
|
|
8
|
+
* different arguments or tags than the default locale's, or a stale index.
|
|
9
|
+
*/
|
|
10
|
+
export function check(dir, defaultLocale) {
|
|
11
|
+
const problems = [];
|
|
12
|
+
const reference = flatten(readLocale(dir, defaultLocale));
|
|
13
|
+
const referenceArgs = new Map();
|
|
14
|
+
for (const [key, message] of reference) {
|
|
15
|
+
const args = argumentsOf(message);
|
|
16
|
+
if (typeof args === 'string')
|
|
17
|
+
problems.push(`${defaultLocale}: ${key}: ${args}`);
|
|
18
|
+
else
|
|
19
|
+
referenceArgs.set(key, args);
|
|
20
|
+
}
|
|
21
|
+
for (const locale of listLocales(dir)) {
|
|
22
|
+
if (locale === defaultLocale)
|
|
23
|
+
continue;
|
|
24
|
+
const messages = flatten(readLocale(dir, locale));
|
|
25
|
+
for (const key of reference.keys()) {
|
|
26
|
+
if (!messages.has(key))
|
|
27
|
+
problems.push(`${locale}: missing ${key}`);
|
|
28
|
+
}
|
|
29
|
+
for (const [key, message] of messages) {
|
|
30
|
+
if (!reference.has(key)) {
|
|
31
|
+
problems.push(`${locale}: ${key} is not in ${defaultLocale}`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const args = argumentsOf(message);
|
|
35
|
+
const want = referenceArgs.get(key);
|
|
36
|
+
if (typeof args === 'string')
|
|
37
|
+
problems.push(`${locale}: ${key}: ${args}`);
|
|
38
|
+
else if (want && args.join() !== want.join()) {
|
|
39
|
+
problems.push(`${locale}: ${key} uses {${args.join(', ')}}, ${defaultLocale} uses {${want.join(', ')}}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (isSplit(dir, defaultLocale)) {
|
|
44
|
+
const file = indexFile(dir, defaultLocale);
|
|
45
|
+
let current = '';
|
|
46
|
+
try {
|
|
47
|
+
current = readFileSync(file, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// A missing index is reported below like a stale one.
|
|
51
|
+
}
|
|
52
|
+
if (current !== indexSource(dir, defaultLocale)) {
|
|
53
|
+
problems.push(`${path.relative(process.cwd(), file)} is out of date: run parallelworks-i18n index`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return problems;
|
|
57
|
+
}
|
|
58
|
+
/** The sorted argument and tag names of an ICU message, or why it doesn't parse. */
|
|
59
|
+
export function argumentsOf(message) {
|
|
60
|
+
let ast;
|
|
61
|
+
try {
|
|
62
|
+
ast = parse(message);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return `not valid ICU: ${error.message}`;
|
|
66
|
+
}
|
|
67
|
+
const names = new Set();
|
|
68
|
+
const walk = (elements) => {
|
|
69
|
+
for (const el of elements) {
|
|
70
|
+
if (el.type === TYPE.tag) {
|
|
71
|
+
names.add(`<${el.value}>`);
|
|
72
|
+
walk(el.children);
|
|
73
|
+
}
|
|
74
|
+
else if (el.type === TYPE.select || el.type === TYPE.plural) {
|
|
75
|
+
names.add(el.value);
|
|
76
|
+
for (const option of Object.values(el.options))
|
|
77
|
+
walk(option.value);
|
|
78
|
+
}
|
|
79
|
+
else if (el.type === TYPE.argument ||
|
|
80
|
+
el.type === TYPE.number ||
|
|
81
|
+
el.type === TYPE.date ||
|
|
82
|
+
el.type === TYPE.time) {
|
|
83
|
+
names.add(el.value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
walk(ast);
|
|
88
|
+
return [...names].sort();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* For a locale kept as one file per namespace, a TypeScript module importing
|
|
92
|
+
* every namespace, so `typeof import('<dir>/<locale>')` types the messages.
|
|
93
|
+
*/
|
|
94
|
+
export function indexSource(dir, locale) {
|
|
95
|
+
const names = localeFiles(dir, locale).map((f) => path.basename(f, '.json'));
|
|
96
|
+
const imports = names.map((n) => `import ${n} from './${n}.json'`).join('\n');
|
|
97
|
+
return `${imports}\n\nexport default {\n${names.map((n) => ` ${n},`).join('\n')}\n}\n`;
|
|
98
|
+
}
|
|
99
|
+
function indexFile(dir, locale) {
|
|
100
|
+
return path.join(dir, locale, 'index.ts');
|
|
101
|
+
}
|
|
102
|
+
export function writeIndex(dir, locale) {
|
|
103
|
+
if (isSplit(dir, locale))
|
|
104
|
+
writeFileSync(indexFile(dir, locale), indexSource(dir, locale));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Merges additions into dir's catalogs without changing an existing message.
|
|
108
|
+
* additions is one locale's tree for defaultLocale, or trees keyed by locale.
|
|
109
|
+
* Safe to run from several processes at once.
|
|
110
|
+
*/
|
|
111
|
+
export function add(dir, defaultLocale, additions) {
|
|
112
|
+
const locales = listLocales(dir);
|
|
113
|
+
const keys = Object.keys(additions);
|
|
114
|
+
const byLocale = keys.length > 0 && keys.every((k) => locales.includes(k))
|
|
115
|
+
? additions
|
|
116
|
+
: { [defaultLocale]: additions };
|
|
117
|
+
const lock = path.join(dir, '.add.lock');
|
|
118
|
+
for (let i = 0;; i++) {
|
|
119
|
+
try {
|
|
120
|
+
mkdirSync(lock);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
if (i > 200)
|
|
125
|
+
throw new Error(`${lock} is held: remove it if no other add is running`);
|
|
126
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
// Merge everything before writing anything, so a conflict changes no file.
|
|
131
|
+
const writes = Object.entries(byLocale).flatMap(([locale, tree]) => merged(dir, locale, tree));
|
|
132
|
+
for (const [file, value] of writes)
|
|
133
|
+
writeJSON(file, value);
|
|
134
|
+
for (const locale of Object.keys(byLocale))
|
|
135
|
+
writeIndex(dir, locale);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
rmdirSync(lock);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function merged(dir, locale, tree) {
|
|
142
|
+
if (!isSplit(dir, locale)) {
|
|
143
|
+
const file = path.join(dir, `${locale}.json`);
|
|
144
|
+
return [[file, merge(readJSON(file), tree, locale)]];
|
|
145
|
+
}
|
|
146
|
+
return Object.entries(tree).map(([ns, value]) => {
|
|
147
|
+
if (typeof value === 'string')
|
|
148
|
+
throw new Error(`${locale}: ${ns}: a namespace must be an object`);
|
|
149
|
+
const file = path.join(dir, locale, `${ns}.json`);
|
|
150
|
+
let existing = {};
|
|
151
|
+
try {
|
|
152
|
+
existing = readJSON(file);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// A new namespace.
|
|
156
|
+
}
|
|
157
|
+
return [file, merge(existing, value, `${locale}.${ns}`)];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function merge(into, from, at) {
|
|
161
|
+
for (const [key, value] of Object.entries(from)) {
|
|
162
|
+
const existing = into[key];
|
|
163
|
+
const where = at ? `${at}.${key}` : key;
|
|
164
|
+
if (existing === undefined)
|
|
165
|
+
into[key] = value;
|
|
166
|
+
else if (typeof existing === 'object' && typeof value === 'object')
|
|
167
|
+
merge(existing, value, where);
|
|
168
|
+
else if (existing !== value)
|
|
169
|
+
throw new Error(`${where} already exists as ${JSON.stringify(existing)}`);
|
|
170
|
+
}
|
|
171
|
+
return into;
|
|
172
|
+
}
|
|
173
|
+
function readJSON(file) {
|
|
174
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
175
|
+
}
|
|
176
|
+
function writeJSON(file, value) {
|
|
177
|
+
writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { add, check, writeIndex } from './check.js';
|
|
5
|
+
const usage = `usage: parallelworks-i18n <command> [--dir <locales>] [--default <locale>]
|
|
6
|
+
|
|
7
|
+
check every locale has the default locale's keys, valid ICU messages
|
|
8
|
+
and the same arguments; a split locale's index.ts is current
|
|
9
|
+
index rewrite the default locale's index.ts (one-file-per-namespace layout)
|
|
10
|
+
add <file> merge messages from a JSON file without changing existing ones;
|
|
11
|
+
the file is one locale's tree, or trees keyed by locale
|
|
12
|
+
|
|
13
|
+
--dir locales directory (default: src/i18n/locales)
|
|
14
|
+
--default default locale (default: en)`;
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const flag = (name, fallback) => {
|
|
17
|
+
const i = args.indexOf(`--${name}`);
|
|
18
|
+
return i >= 0 && args[i + 1] ? (args[i + 1] ?? fallback) : fallback;
|
|
19
|
+
};
|
|
20
|
+
const dir = path.resolve(flag('dir', 'src/i18n/locales'));
|
|
21
|
+
const defaultLocale = flag('default', 'en');
|
|
22
|
+
const [command, file] = args.filter((a, i) => !a.startsWith('--') && !args[i - 1]?.startsWith('--'));
|
|
23
|
+
if (command === 'check') {
|
|
24
|
+
const problems = check(dir, defaultLocale);
|
|
25
|
+
for (const problem of problems)
|
|
26
|
+
console.error(problem);
|
|
27
|
+
if (problems.length > 0)
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
else
|
|
30
|
+
console.log(`i18n: catalogs in ${path.relative(process.cwd(), dir) || '.'} are consistent`);
|
|
31
|
+
}
|
|
32
|
+
else if (command === 'index') {
|
|
33
|
+
writeIndex(dir, defaultLocale);
|
|
34
|
+
}
|
|
35
|
+
else if (command === 'add' && file) {
|
|
36
|
+
add(dir, defaultLocale, JSON.parse(readFileSync(file, 'utf8')));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.error(usage);
|
|
40
|
+
process.exitCode = 2;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEnD,MAAM,KAAK,GAAG;;;;;;;;;0CAS4B,CAAA;AAE1C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAE,EAAE;IAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IACnC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;AACrE,CAAC,CAAA;AACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAA;AACzD,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;AAC3C,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AAEpG,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IACxB,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;IAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ;QAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACtD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;;QACxC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,IAAI,GAAG,iBAAiB,CAAC,CAAA;AAClG,CAAC;KAAM,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IAC/B,UAAU,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;AAChC,CAAC;KAAM,IAAI,OAAO,KAAK,KAAK,IAAI,IAAI,EAAE,CAAC;IACrC,GAAG,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACpB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACtB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The supported locale that best matches preferred, most preferred first (as in
|
|
3
|
+
* navigator.languages): an exact match, else the same base language, so es-MX
|
|
4
|
+
* gets es. Returns fallback when nothing matches.
|
|
5
|
+
*/
|
|
6
|
+
export declare function negotiateLocale(preferred: readonly string[], locales: readonly string[], fallback: string): string;
|
|
7
|
+
export interface DetectOptions {
|
|
8
|
+
/** Returned when nothing else matches. */
|
|
9
|
+
fallback: string;
|
|
10
|
+
/** A locale the server chose, such as one it injected into the page. */
|
|
11
|
+
injected?: string | null | undefined;
|
|
12
|
+
/** A cookie holding the person's choice. */
|
|
13
|
+
cookie?: string;
|
|
14
|
+
/** A localStorage key holding the person's choice. */
|
|
15
|
+
storageKey?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The locale to start in: the server's choice, then a stored choice (cookie,
|
|
19
|
+
* then localStorage), then the browser's languages. Each is negotiated against
|
|
20
|
+
* locales, so a stored es-MX still finds es.
|
|
21
|
+
*/
|
|
22
|
+
export declare function detectLocale(locales: readonly string[], options: DetectOptions): string;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,QAAQ,EAAE,MAAM,GACf,MAAM,CASR;AAUD,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACpC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAUvF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The supported locale that best matches preferred, most preferred first (as in
|
|
3
|
+
* navigator.languages): an exact match, else the same base language, so es-MX
|
|
4
|
+
* gets es. Returns fallback when nothing matches.
|
|
5
|
+
*/
|
|
6
|
+
export function negotiateLocale(preferred, locales, fallback) {
|
|
7
|
+
for (const tag of preferred) {
|
|
8
|
+
const exact = locales.find((l) => l.toLowerCase() === tag.toLowerCase());
|
|
9
|
+
if (exact)
|
|
10
|
+
return exact;
|
|
11
|
+
const base = baseLanguage(tag);
|
|
12
|
+
const match = base && locales.find((l) => baseLanguage(l) === base);
|
|
13
|
+
if (match)
|
|
14
|
+
return match;
|
|
15
|
+
}
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
function baseLanguage(tag) {
|
|
19
|
+
try {
|
|
20
|
+
return new Intl.Locale(tag).language;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The locale to start in: the server's choice, then a stored choice (cookie,
|
|
28
|
+
* then localStorage), then the browser's languages. Each is negotiated against
|
|
29
|
+
* locales, so a stored es-MX still finds es.
|
|
30
|
+
*/
|
|
31
|
+
export function detectLocale(locales, options) {
|
|
32
|
+
const choices = [options.injected, readCookie(options.cookie), readStorage(options.storageKey)];
|
|
33
|
+
for (const choice of choices) {
|
|
34
|
+
if (choice) {
|
|
35
|
+
const match = negotiateLocale([choice], locales, '');
|
|
36
|
+
if (match)
|
|
37
|
+
return match;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const browser = typeof navigator === 'undefined' ? [] : navigator.languages;
|
|
41
|
+
return negotiateLocale(browser, locales, options.fallback);
|
|
42
|
+
}
|
|
43
|
+
function readCookie(name) {
|
|
44
|
+
if (!name || typeof document === 'undefined')
|
|
45
|
+
return undefined;
|
|
46
|
+
for (const part of document.cookie.split(';')) {
|
|
47
|
+
const [key, ...value] = part.trim().split('=');
|
|
48
|
+
if (key === name)
|
|
49
|
+
return decodeURIComponent(value.join('='));
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function readStorage(key) {
|
|
54
|
+
if (!key)
|
|
55
|
+
return undefined;
|
|
56
|
+
try {
|
|
57
|
+
return localStorage.getItem(key) ?? undefined;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Storage blocked, as in some private windows: use the browser's languages.
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,SAA4B,EAC5B,OAA0B,EAC1B,QAAgB;IAEhB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;QACxE,IAAI,KAAK;YAAE,OAAO,KAAK,CAAA;QACvB,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;QAC9B,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;QACnE,IAAI,KAAK;YAAE,OAAO,KAAK,CAAA;IACzB,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAaD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAA0B,EAAE,OAAsB;IAC7E,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAA;IAC/F,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,KAAK,GAAG,eAAe,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;YACpD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAA;QACzB,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAA;IAC3E,OAAO,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;AAC5D,CAAC;AAED,SAAS,UAAU,CAAC,IAAwB;IAC1C,IAAI,CAAC,IAAI,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,SAAS,CAAA;IAC9D,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC9C,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,WAAW,CAAC,GAAuB;IAC1C,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAC1B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,SAAS,CAAA;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC"}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,MAAM,EACN,OAAO,EACP,OAAO,EACP,WAAW,EACX,WAAW,EACX,KAAK,QAAQ,EACb,UAAU,EACV,aAAa,GACd,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/node.js
ADDED
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EACN,OAAO,EACP,OAAO,EACP,WAAW,EACX,WAAW,EAEX,UAAU,EACV,aAAa,GACd,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type AbstractIntlMessages } from 'use-intl';
|
|
3
|
+
/**
|
|
4
|
+
* Loads a locale's messages: the ones every page needs, or with catalog, one
|
|
5
|
+
* feature's, loaded only where that feature is shown. The Vite plugin in
|
|
6
|
+
* @parallelworks/i18n/vite generates one.
|
|
7
|
+
*/
|
|
8
|
+
export type LoadMessages = (locale: string, catalog?: string) => Promise<AbstractIntlMessages>;
|
|
9
|
+
/** Switches the app's language; resolves once the new messages are shown. */
|
|
10
|
+
export declare function useSetLocale(): (locale: string) => Promise<void>;
|
|
11
|
+
export declare function LocaleProvider({ initial, load, fallbackLocale, onChange, timeZone, children, }: {
|
|
12
|
+
initial: {
|
|
13
|
+
locale: string;
|
|
14
|
+
messages: AbstractIntlMessages;
|
|
15
|
+
};
|
|
16
|
+
load: LoadMessages;
|
|
17
|
+
/** Loaded instead when a locale's messages fail to load. */
|
|
18
|
+
fallbackLocale?: string;
|
|
19
|
+
/** Runs after the language changes: store the choice, update a date library's locale. */
|
|
20
|
+
onChange?: (locale: string) => void;
|
|
21
|
+
/** For formatting dates and times; the browser's own by default. */
|
|
22
|
+
timeZone?: string;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
25
|
+
/**
|
|
26
|
+
* Adds one feature's messages to everything beneath it, loading them for the
|
|
27
|
+
* current language first. fallback shows while they load.
|
|
28
|
+
*/
|
|
29
|
+
export declare function Catalog({ name, fallback, children, }: {
|
|
30
|
+
name: string;
|
|
31
|
+
fallback?: ReactNode;
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
}): import("react").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAUf,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,KAAK,oBAAoB,EAAwC,MAAM,UAAU,CAAA;AAE1F;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;AAe9F,6EAA6E;AAC7E,wBAAgB,YAAY,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAEhE;AAID,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,cAAc,EACd,QAAQ,EACR,QAA0B,EAC1B,QAAQ,GACT,EAAE;IACD,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,oBAAoB,CAAA;KAAE,CAAA;IAC3D,IAAI,EAAE,YAAY,CAAA;IAClB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,SAAS,CAAA;CACpB,+BA4BA;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,QAAe,EACf,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,QAAQ,EAAE,SAAS,CAAA;CACpB,+BAMA"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, Suspense, startTransition, use, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { IntlProvider, useLocale, useMessages } from 'use-intl';
|
|
4
|
+
const LocalesContext = createContext(null);
|
|
5
|
+
function useLocales() {
|
|
6
|
+
const locales = useContext(LocalesContext);
|
|
7
|
+
if (!locales)
|
|
8
|
+
throw new Error('@parallelworks/i18n: wrap the app in <LocaleProvider>');
|
|
9
|
+
return locales;
|
|
10
|
+
}
|
|
11
|
+
/** Switches the app's language; resolves once the new messages are shown. */
|
|
12
|
+
export function useSetLocale() {
|
|
13
|
+
return useLocales().setLocale;
|
|
14
|
+
}
|
|
15
|
+
const browserTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
16
|
+
export function LocaleProvider({ initial, load, fallbackLocale, onChange, timeZone = browserTimeZone, children, }) {
|
|
17
|
+
const [current, setCurrent] = useState(initial);
|
|
18
|
+
const cached = useMemo(() => cache(load, fallbackLocale), [load, fallbackLocale]);
|
|
19
|
+
const latest = useRef(0);
|
|
20
|
+
const setLocale = useCallback(async (locale) => {
|
|
21
|
+
const request = ++latest.current;
|
|
22
|
+
const messages = await cached(locale);
|
|
23
|
+
if (request !== latest.current)
|
|
24
|
+
return; // a later choice won
|
|
25
|
+
startTransition(() => setCurrent({ locale, messages }));
|
|
26
|
+
onChange?.(locale);
|
|
27
|
+
}, [cached, onChange]);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
document.documentElement.lang = current.locale;
|
|
30
|
+
}, [current.locale]);
|
|
31
|
+
const value = useMemo(() => ({ setLocale, load: cached }), [setLocale, cached]);
|
|
32
|
+
return (_jsx(LocalesContext, { value: value, children: _jsx(IntlProvider, { locale: current.locale, messages: current.messages, timeZone: timeZone, children: children }) }));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Adds one feature's messages to everything beneath it, loading them for the
|
|
36
|
+
* current language first. fallback shows while they load.
|
|
37
|
+
*/
|
|
38
|
+
export function Catalog({ name, fallback = null, children, }) {
|
|
39
|
+
return (_jsx(Suspense, { fallback: fallback, children: _jsx(CatalogMessages, { name: name, children: children }) }));
|
|
40
|
+
}
|
|
41
|
+
function CatalogMessages({ name, children }) {
|
|
42
|
+
const locale = useLocale();
|
|
43
|
+
const shared = useMessages();
|
|
44
|
+
const feature = use(useLocales().load(locale, name));
|
|
45
|
+
const messages = useMemo(() => ({ ...shared, ...feature }), [shared, feature]);
|
|
46
|
+
return (_jsx(IntlProvider, { locale: locale, messages: messages, children: children }));
|
|
47
|
+
}
|
|
48
|
+
/** Keeps one promise per locale and catalog, so use() sees the same one each render. */
|
|
49
|
+
function cache(load, fallbackLocale) {
|
|
50
|
+
const pending = new Map();
|
|
51
|
+
const cached = (locale, catalog) => {
|
|
52
|
+
const key = `${locale}/${catalog ?? ''}`;
|
|
53
|
+
let promise = pending.get(key);
|
|
54
|
+
if (!promise) {
|
|
55
|
+
promise = load(locale, catalog).catch((error) => {
|
|
56
|
+
if (fallbackLocale && locale !== fallbackLocale)
|
|
57
|
+
return cached(fallbackLocale, catalog);
|
|
58
|
+
// Forget the failure so the next render tries again.
|
|
59
|
+
pending.delete(key);
|
|
60
|
+
throw error;
|
|
61
|
+
});
|
|
62
|
+
pending.set(key, promise);
|
|
63
|
+
}
|
|
64
|
+
return promise;
|
|
65
|
+
};
|
|
66
|
+
return cached;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import { type CatalogMap } from './catalogs.js';
|
|
3
|
+
export interface I18nOptions {
|
|
4
|
+
/** The locales directory, relative to the Vite root. */
|
|
5
|
+
dir: string;
|
|
6
|
+
/** The locale whose common messages are bundled with the app, and the fallback. */
|
|
7
|
+
defaultLocale: string;
|
|
8
|
+
/** Feature catalogs, loaded only where <Catalog name=...> is rendered. */
|
|
9
|
+
catalogs?: CatalogMap;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Serves `virtual:i18n`: `locales`, `defaultLocale`, `defaultMessages` (bundled)
|
|
13
|
+
* and `loadMessages(locale, catalog?)`, which loads every other locale and
|
|
14
|
+
* catalog as its own chunk. Add `@parallelworks/i18n/client` to tsconfig types.
|
|
15
|
+
*/
|
|
16
|
+
export declare function i18n(options: I18nOptions): Plugin;
|
|
17
|
+
//# sourceMappingURL=vite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAClC,OAAO,EACL,KAAK,UAAU,EAMhB,MAAM,eAAe,CAAA;AAKtB,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAA;IACX,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,UAAU,CAAA;CACtB;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA4DjD"}
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { COMMON, listLocales, localeFiles, readLocale, selectCatalog, } from './catalogs.js';
|
|
3
|
+
const id = 'virtual:i18n';
|
|
4
|
+
const resolved = `\0${id}`;
|
|
5
|
+
/**
|
|
6
|
+
* Serves `virtual:i18n`: `locales`, `defaultLocale`, `defaultMessages` (bundled)
|
|
7
|
+
* and `loadMessages(locale, catalog?)`, which loads every other locale and
|
|
8
|
+
* catalog as its own chunk. Add `@parallelworks/i18n/client` to tsconfig types.
|
|
9
|
+
*/
|
|
10
|
+
export function i18n(options) {
|
|
11
|
+
const catalogs = options.catalogs ?? {};
|
|
12
|
+
const names = [COMMON, ...Object.keys(catalogs)];
|
|
13
|
+
let dir = '';
|
|
14
|
+
let locales = [];
|
|
15
|
+
return {
|
|
16
|
+
name: '@parallelworks/i18n',
|
|
17
|
+
configResolved(config) {
|
|
18
|
+
dir = path.resolve(config.root, options.dir);
|
|
19
|
+
locales = listLocales(dir);
|
|
20
|
+
if (!locales.includes(options.defaultLocale)) {
|
|
21
|
+
throw new Error(`@parallelworks/i18n: no ${options.defaultLocale} messages in ${dir}`);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
resolveId(source) {
|
|
25
|
+
return source === id || source.startsWith(`${id}/`) ? `\0${source}` : undefined;
|
|
26
|
+
},
|
|
27
|
+
load(source) {
|
|
28
|
+
if (source === resolved) {
|
|
29
|
+
const loaders = locales.map((locale) => `${JSON.stringify(locale)}: {${names
|
|
30
|
+
.map((name) =>
|
|
31
|
+
// The default locale's common messages are already bundled.
|
|
32
|
+
locale === options.defaultLocale && name === COMMON
|
|
33
|
+
? `${JSON.stringify(name)}: () => Promise.resolve({ default: defaultMessages })`
|
|
34
|
+
: `${JSON.stringify(name)}: () => import(${JSON.stringify(`${id}/${locale}/${name}`)})`)
|
|
35
|
+
.join(',')}}`);
|
|
36
|
+
return `import defaultMessages from ${JSON.stringify(`${id}/${options.defaultLocale}/${COMMON}`)}
|
|
37
|
+
export { defaultMessages }
|
|
38
|
+
export const defaultLocale = ${JSON.stringify(options.defaultLocale)}
|
|
39
|
+
export const locales = ${JSON.stringify(locales)}
|
|
40
|
+
const loaders = {${loaders.join(',')}}
|
|
41
|
+
export function loadMessages(locale, catalog = ${JSON.stringify(COMMON)}) {
|
|
42
|
+
const load = loaders[locale]?.[catalog]
|
|
43
|
+
if (!load) return Promise.reject(new Error('no messages for ' + locale + '/' + catalog))
|
|
44
|
+
return load().then((m) => m.default)
|
|
45
|
+
}`;
|
|
46
|
+
}
|
|
47
|
+
if (!source.startsWith(`${resolved}/`))
|
|
48
|
+
return undefined;
|
|
49
|
+
const [locale = '', name = ''] = source.slice(resolved.length + 1).split('/');
|
|
50
|
+
if (!locales.includes(locale) || !names.includes(name)) {
|
|
51
|
+
this.error(`unknown message catalog ${locale}/${name}`);
|
|
52
|
+
}
|
|
53
|
+
for (const file of localeFiles(dir, locale))
|
|
54
|
+
this.addWatchFile(file);
|
|
55
|
+
const messages = selectCatalog(readLocale(dir, locale), name, catalogs);
|
|
56
|
+
return `export default ${JSON.stringify(messages)}`;
|
|
57
|
+
},
|
|
58
|
+
handleHotUpdate({ file, server }) {
|
|
59
|
+
if (!file.startsWith(`${dir}${path.sep}`) || !file.endsWith('.json'))
|
|
60
|
+
return undefined;
|
|
61
|
+
for (const module of server.moduleGraph.idToModuleMap.values()) {
|
|
62
|
+
if (module.id?.startsWith(resolved))
|
|
63
|
+
server.moduleGraph.invalidateModule(module);
|
|
64
|
+
}
|
|
65
|
+
server.ws.send({ type: 'full-reload' });
|
|
66
|
+
return [];
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,80 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parallelworks/i18n",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Localization for React apps on use-intl: locale negotiation, a provider, lazily loaded catalogs and catalog checks",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/parallelworks/foundation.git",
|
|
9
|
+
"directory": "packages/i18n"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./react": {
|
|
19
|
+
"types": "./dist/react.d.ts",
|
|
20
|
+
"default": "./dist/react.js"
|
|
21
|
+
},
|
|
22
|
+
"./vite": {
|
|
23
|
+
"types": "./dist/vite.d.ts",
|
|
24
|
+
"default": "./dist/vite.js"
|
|
25
|
+
},
|
|
26
|
+
"./node": {
|
|
27
|
+
"types": "./dist/node.d.ts",
|
|
28
|
+
"default": "./dist/node.js"
|
|
29
|
+
},
|
|
30
|
+
"./client": {
|
|
31
|
+
"types": "./client.d.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"bin": {
|
|
35
|
+
"parallelworks-i18n": "./dist/cli.js"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"registry": "https://registry.npmjs.org",
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"client.d.ts"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@formatjs/icu-messageformat-parser": "^3.5.20"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": "^19.0.0",
|
|
50
|
+
"use-intl": "^4.0.0",
|
|
51
|
+
"vite": ">=6.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"react": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"use-intl": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"vite": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^26.6.3",
|
|
66
|
+
"@types/react": "^19.2.0",
|
|
67
|
+
"@types/react-dom": "^19.2.0",
|
|
68
|
+
"react": "^19.3.0",
|
|
69
|
+
"react-dom": "^19.3.0",
|
|
70
|
+
"typescript": "^7.0.2",
|
|
71
|
+
"use-intl": "^4.13.0",
|
|
72
|
+
"vite": "^8.0.0",
|
|
73
|
+
"vitest": "^4.1.8"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
77
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
78
|
+
"test": "vitest run"
|
|
79
|
+
}
|
|
6
80
|
}
|