@byline/i18n 4.11.1 → 4.12.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/README.md +13 -13
- package/dist/admin/index.d.ts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/resolve.d.ts +4 -4
- package/dist/resolve.js +2 -2
- package/dist/types.d.ts +2 -2
- package/package.json +11 -11
- package/src/admin/index.ts +2 -2
- package/src/index.ts +2 -2
- package/src/resolve.test.node.ts +16 -16
- package/src/resolve.ts +4 -4
- package/src/types.ts +2 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The package bundles every official Byline admin translation as a JSON file under
|
|
|
17
17
|
## Three entry points
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
import { mergeTranslations, createFormatter,
|
|
20
|
+
import { mergeTranslations, createFormatter, resolveAdminLocale } from '@byline/i18n'
|
|
21
21
|
import { I18nProvider, useTranslation, LanguageMenu } from '@byline/i18n/react'
|
|
22
22
|
import { adminTranslations, en } from '@byline/i18n/admin'
|
|
23
23
|
```
|
|
@@ -30,19 +30,19 @@ import { adminTranslations, en } from '@byline/i18n/admin'
|
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
32
|
// apps/your-app/byline/admin.config.ts
|
|
33
|
-
import {
|
|
33
|
+
import { defineAdminConfig } from '@byline/core'
|
|
34
34
|
import { adminTranslations } from '@byline/i18n/admin'
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
defineAdminConfig({
|
|
37
37
|
i18n: {
|
|
38
|
-
|
|
38
|
+
admin: {
|
|
39
39
|
defaultLocale: 'en',
|
|
40
40
|
locales: ['en'],
|
|
41
41
|
},
|
|
42
42
|
// … content locales as before
|
|
43
43
|
translations: adminTranslations({ locales: ['en'] }),
|
|
44
44
|
},
|
|
45
|
-
// … the rest of your
|
|
45
|
+
// … the rest of your admin config
|
|
46
46
|
})
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -52,7 +52,7 @@ defineClientConfig({
|
|
|
52
52
|
import { adminTranslations } from '@byline/i18n/admin'
|
|
53
53
|
|
|
54
54
|
i18n: {
|
|
55
|
-
|
|
55
|
+
admin: { defaultLocale: 'en', locales: ['en', 'fr'] },
|
|
56
56
|
translations: adminTranslations({ locales: ['en', 'fr'] }),
|
|
57
57
|
}
|
|
58
58
|
```
|
|
@@ -63,7 +63,7 @@ The set of bundled locales is exported as `bundledLocales` for hosts that want t
|
|
|
63
63
|
import { adminTranslations, bundledLocales } from '@byline/i18n/admin'
|
|
64
64
|
|
|
65
65
|
i18n: {
|
|
66
|
-
|
|
66
|
+
admin: { defaultLocale: 'en', locales: [...bundledLocales] },
|
|
67
67
|
translations: adminTranslations({ locales: bundledLocales }),
|
|
68
68
|
}
|
|
69
69
|
```
|
|
@@ -72,7 +72,7 @@ i18n: {
|
|
|
72
72
|
|
|
73
73
|
## Plugin contributions
|
|
74
74
|
|
|
75
|
-
A plugin (richtext extension, custom field, AI tool, …) ships its own JSON files inside its own package and exposes a factory matching `adminTranslations`'s shape — takes `{ locales }`, returns a `TranslationBundle` for the plugin's own namespace. The host merges them in `
|
|
75
|
+
A plugin (richtext extension, custom field, AI tool, …) ships its own JSON files inside its own package and exposes a factory matching `adminTranslations`'s shape — takes `{ locales }`, returns a `TranslationBundle` for the plugin's own namespace. The host merges them in `defineAdminConfig`:
|
|
76
76
|
|
|
77
77
|
```ts
|
|
78
78
|
import { mergeTranslations } from '@byline/i18n'
|
|
@@ -80,7 +80,7 @@ import { adminTranslations } from '@byline/i18n/admin'
|
|
|
80
80
|
import { aiTranslations } from '@byline/ai/i18n'
|
|
81
81
|
|
|
82
82
|
i18n: {
|
|
83
|
-
|
|
83
|
+
admin: { defaultLocale: 'en', locales: ['en', 'fr'] },
|
|
84
84
|
translations: mergeTranslations(
|
|
85
85
|
adminTranslations({ locales: ['en', 'fr'] }),
|
|
86
86
|
aiTranslations({ locales: ['en', 'fr'] }),
|
|
@@ -88,7 +88,7 @@ i18n: {
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
A plugin that ships a locale the host hasn't enabled is harmless — the plugin's factory simply returns nothing for that code, the merge produces an empty entry for it, and the boot validator gates against `i18n.
|
|
91
|
+
A plugin that ships a locale the host hasn't enabled is harmless — the plugin's factory simply returns nothing for that code, the merge produces an empty entry for it, and the boot validator gates against `i18n.admin.locales` anyway.
|
|
92
92
|
|
|
93
93
|
`mergeTranslations` is associative + deterministic; later sources override earlier ones at the `(locale, namespace, key)` grain, and a `MergeOptions.onCollision` callback is available for surfacing conflicts during development.
|
|
94
94
|
|
|
@@ -138,12 +138,12 @@ In a TanStack Start host, `resolveServerTranslator(namespace)` (provided by `@by
|
|
|
138
138
|
|
|
139
139
|
## Locale resolution cascade
|
|
140
140
|
|
|
141
|
-
`
|
|
141
|
+
`resolveAdminLocale` is the same pure cascade run on client and server, so SSR and hydration produce the same answer:
|
|
142
142
|
|
|
143
143
|
```ts
|
|
144
|
-
import {
|
|
144
|
+
import { resolveAdminLocale } from '@byline/i18n'
|
|
145
145
|
|
|
146
|
-
const activeLocale =
|
|
146
|
+
const activeLocale = resolveAdminLocale({
|
|
147
147
|
locales: ['en', 'fr'],
|
|
148
148
|
defaultLocale: 'en',
|
|
149
149
|
preferred: actor?.admin_user?.preferred_locale,
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -36,9 +36,9 @@ export interface AdminTranslationsOptions {
|
|
|
36
36
|
* ```ts
|
|
37
37
|
* import { adminTranslations } from '@byline/i18n/admin'
|
|
38
38
|
*
|
|
39
|
-
*
|
|
39
|
+
* defineAdminConfig({
|
|
40
40
|
* i18n: {
|
|
41
|
-
*
|
|
41
|
+
* admin: { defaultLocale: 'en', locales: ['en', 'fr'] },
|
|
42
42
|
* translations: adminTranslations({ locales: ['en', 'fr'] }),
|
|
43
43
|
* },
|
|
44
44
|
* })
|
package/dist/index.d.ts
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
*/
|
|
27
27
|
export { createFormatter } from './formatter.js';
|
|
28
28
|
export { mergeTranslations } from './merge.js';
|
|
29
|
-
export {
|
|
29
|
+
export { resolveAdminLocale } from './resolve.js';
|
|
30
30
|
export type { Formatter, FormatterOptions, MissingTranslationEvent, } from './formatter.js';
|
|
31
31
|
export type { MergeOptions, TranslationCollision } from './merge.js';
|
|
32
|
-
export type {
|
|
32
|
+
export type { ResolveAdminLocaleOptions } from './resolve.js';
|
|
33
33
|
export type { LocaleCode, LocaleDefinition, MessageKey, Namespace, NamespaceTranslations, TranslationBundle, TranslationValues, } from './types.js';
|
package/dist/index.js
CHANGED
package/dist/resolve.d.ts
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import type { LocaleCode } from './types.js';
|
|
9
|
-
export interface
|
|
10
|
-
/** Permitted locale set — from `i18n.
|
|
9
|
+
export interface ResolveAdminLocaleOptions {
|
|
10
|
+
/** Permitted locale set — from `i18n.admin.locales`. */
|
|
11
11
|
locales: readonly LocaleCode[];
|
|
12
|
-
/** Last-resort fallback — from `i18n.
|
|
12
|
+
/** Last-resort fallback — from `i18n.admin.defaultLocale`. */
|
|
13
13
|
defaultLocale: LocaleCode;
|
|
14
14
|
/** `admin_users.preferred_locale` for the authenticated request, if any. */
|
|
15
15
|
preferred?: LocaleCode | null;
|
|
@@ -18,4 +18,4 @@ export interface ResolveInterfaceLocaleOptions {
|
|
|
18
18
|
/** Raw `Accept-Language` request header. */
|
|
19
19
|
acceptLanguage?: string | null;
|
|
20
20
|
}
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function resolveAdminLocale(options: ResolveAdminLocaleOptions): LocaleCode;
|
package/dist/resolve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { match } from "@formatjs/intl-localematcher";
|
|
2
2
|
import negotiator_0 from "negotiator";
|
|
3
|
-
function
|
|
3
|
+
function resolveAdminLocale(options) {
|
|
4
4
|
const { locales, defaultLocale, preferred, cookie, acceptLanguage } = options;
|
|
5
5
|
if (null != preferred && locales.includes(preferred)) return preferred;
|
|
6
6
|
if (null != cookie && locales.includes(cookie)) return cookie;
|
|
@@ -25,4 +25,4 @@ function negotiateAcceptLanguage(header, locales, defaultLocale) {
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
export {
|
|
28
|
+
export { resolveAdminLocale };
|
package/dist/types.d.ts
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* Locale code — kept as a plain `string` so the package stays open to
|
|
10
10
|
* any BCP 47 tag (`en`, `pt-BR`, `zh-Hans-CN`). The host's
|
|
11
|
-
* `i18n.
|
|
11
|
+
* `i18n.admin.locales` config is the canonical allow-list at
|
|
12
12
|
* runtime; the resolver / validator narrow against it.
|
|
13
13
|
*/
|
|
14
14
|
export type LocaleCode = string;
|
|
15
15
|
/**
|
|
16
16
|
* Lightweight locale definition used by `<LanguageMenu>` to render the
|
|
17
|
-
* dropdown. The `code` matches a string in `i18n.
|
|
17
|
+
* dropdown. The `code` matches a string in `i18n.admin.locales`;
|
|
18
18
|
* the `nativeName` is what the user sees ("English", "Español",
|
|
19
19
|
* "Français", "Deutsch", "日本語").
|
|
20
20
|
*/
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.12.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -53,29 +53,29 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@formatjs/intl-localematcher": "^0.8.13",
|
|
55
55
|
"clsx": "^2.1.1",
|
|
56
|
-
"intl-messageformat": "^11.2.
|
|
56
|
+
"intl-messageformat": "^11.2.13",
|
|
57
57
|
"negotiator": "^1.0.0",
|
|
58
|
-
"@byline/ui": "4.
|
|
58
|
+
"@byline/ui": "4.12.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"react": "^19.0.0",
|
|
62
62
|
"react-dom": "^19.0.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@biomejs/biome": "2.5.
|
|
65
|
+
"@biomejs/biome": "2.5.6",
|
|
66
66
|
"@rsbuild/plugin-react": "^2.1.0",
|
|
67
67
|
"@rslib/core": "^0.23.2",
|
|
68
68
|
"@types/negotiator": "^0.6.4",
|
|
69
|
-
"@types/node": "^26.1.
|
|
70
|
-
"@types/react": "19.2.
|
|
71
|
-
"@types/react-dom": "19.2.
|
|
72
|
-
"@vitejs/plugin-react": "^6.0.
|
|
69
|
+
"@types/node": "^26.1.2",
|
|
70
|
+
"@types/react": "19.2.18",
|
|
71
|
+
"@types/react-dom": "19.2.4",
|
|
72
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
73
73
|
"npm-run-all": "^4.1.5",
|
|
74
|
-
"react": "^19.2.
|
|
75
|
-
"react-dom": "^19.2.
|
|
74
|
+
"react": "^19.2.8",
|
|
75
|
+
"react-dom": "^19.2.8",
|
|
76
76
|
"rimraf": "^6.1.3",
|
|
77
77
|
"typescript": "^7.0.2",
|
|
78
|
-
"vite": "^8.
|
|
78
|
+
"vite": "^8.2.0",
|
|
79
79
|
"vitest": "^4.1.10"
|
|
80
80
|
},
|
|
81
81
|
"publishConfig": {
|
package/src/admin/index.ts
CHANGED
|
@@ -86,9 +86,9 @@ export interface AdminTranslationsOptions {
|
|
|
86
86
|
* ```ts
|
|
87
87
|
* import { adminTranslations } from '@byline/i18n/admin'
|
|
88
88
|
*
|
|
89
|
-
*
|
|
89
|
+
* defineAdminConfig({
|
|
90
90
|
* i18n: {
|
|
91
|
-
*
|
|
91
|
+
* admin: { defaultLocale: 'en', locales: ['en', 'fr'] },
|
|
92
92
|
* translations: adminTranslations({ locales: ['en', 'fr'] }),
|
|
93
93
|
* },
|
|
94
94
|
* })
|
package/src/index.ts
CHANGED
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
|
|
29
29
|
export { createFormatter } from './formatter.js'
|
|
30
30
|
export { mergeTranslations } from './merge.js'
|
|
31
|
-
export {
|
|
31
|
+
export { resolveAdminLocale } from './resolve.js'
|
|
32
32
|
export type {
|
|
33
33
|
Formatter,
|
|
34
34
|
FormatterOptions,
|
|
35
35
|
MissingTranslationEvent,
|
|
36
36
|
} from './formatter.js'
|
|
37
37
|
export type { MergeOptions, TranslationCollision } from './merge.js'
|
|
38
|
-
export type {
|
|
38
|
+
export type { ResolveAdminLocaleOptions } from './resolve.js'
|
|
39
39
|
export type {
|
|
40
40
|
LocaleCode,
|
|
41
41
|
LocaleDefinition,
|
package/src/resolve.test.node.ts
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import { describe, expect, it } from 'vitest'
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { resolveAdminLocale } from './resolve.js'
|
|
12
12
|
|
|
13
13
|
const locales = ['en', 'fr', 'es']
|
|
14
14
|
const defaultLocale = 'en'
|
|
15
15
|
|
|
16
|
-
describe('
|
|
16
|
+
describe('resolveAdminLocale — tier 1 (preferred)', () => {
|
|
17
17
|
it('wins over every other signal when set and valid', () => {
|
|
18
|
-
const out =
|
|
18
|
+
const out = resolveAdminLocale({
|
|
19
19
|
locales,
|
|
20
20
|
defaultLocale,
|
|
21
21
|
preferred: 'fr',
|
|
@@ -26,7 +26,7 @@ describe('resolveInterfaceLocale — tier 1 (preferred)', () => {
|
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
it('falls through when preferred is not in the permitted set', () => {
|
|
29
|
-
const out =
|
|
29
|
+
const out = resolveAdminLocale({
|
|
30
30
|
locales,
|
|
31
31
|
defaultLocale,
|
|
32
32
|
preferred: 'de',
|
|
@@ -36,7 +36,7 @@ describe('resolveInterfaceLocale — tier 1 (preferred)', () => {
|
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
it('falls through when preferred is null', () => {
|
|
39
|
-
const out =
|
|
39
|
+
const out = resolveAdminLocale({
|
|
40
40
|
locales,
|
|
41
41
|
defaultLocale,
|
|
42
42
|
preferred: null,
|
|
@@ -46,9 +46,9 @@ describe('resolveInterfaceLocale — tier 1 (preferred)', () => {
|
|
|
46
46
|
})
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
describe('
|
|
49
|
+
describe('resolveAdminLocale — tier 2 (cookie)', () => {
|
|
50
50
|
it('wins over Accept-Language', () => {
|
|
51
|
-
const out =
|
|
51
|
+
const out = resolveAdminLocale({
|
|
52
52
|
locales,
|
|
53
53
|
defaultLocale,
|
|
54
54
|
cookie: 'es',
|
|
@@ -58,7 +58,7 @@ describe('resolveInterfaceLocale — tier 2 (cookie)', () => {
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
it('falls through when cookie points at a removed locale', () => {
|
|
61
|
-
const out =
|
|
61
|
+
const out = resolveAdminLocale({
|
|
62
62
|
locales,
|
|
63
63
|
defaultLocale,
|
|
64
64
|
cookie: 'de',
|
|
@@ -68,7 +68,7 @@ describe('resolveInterfaceLocale — tier 2 (cookie)', () => {
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
it('falls through when cookie is null', () => {
|
|
71
|
-
const out =
|
|
71
|
+
const out = resolveAdminLocale({
|
|
72
72
|
locales,
|
|
73
73
|
defaultLocale,
|
|
74
74
|
cookie: null,
|
|
@@ -78,9 +78,9 @@ describe('resolveInterfaceLocale — tier 2 (cookie)', () => {
|
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
-
describe('
|
|
81
|
+
describe('resolveAdminLocale — tier 3 (Accept-Language)', () => {
|
|
82
82
|
it('picks the best match from the header', () => {
|
|
83
|
-
const out =
|
|
83
|
+
const out = resolveAdminLocale({
|
|
84
84
|
locales,
|
|
85
85
|
defaultLocale,
|
|
86
86
|
acceptLanguage: 'fr-CA;q=0.9, fr;q=0.8, en;q=0.5',
|
|
@@ -89,7 +89,7 @@ describe('resolveInterfaceLocale — tier 3 (Accept-Language)', () => {
|
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
it('falls back to default when no header language is in the permitted set', () => {
|
|
92
|
-
const out =
|
|
92
|
+
const out = resolveAdminLocale({
|
|
93
93
|
locales,
|
|
94
94
|
defaultLocale,
|
|
95
95
|
acceptLanguage: 'de;q=0.9, ja;q=0.5',
|
|
@@ -98,7 +98,7 @@ describe('resolveInterfaceLocale — tier 3 (Accept-Language)', () => {
|
|
|
98
98
|
})
|
|
99
99
|
|
|
100
100
|
it('handles malformed headers gracefully', () => {
|
|
101
|
-
const out =
|
|
101
|
+
const out = resolveAdminLocale({
|
|
102
102
|
locales,
|
|
103
103
|
defaultLocale,
|
|
104
104
|
acceptLanguage: 'this-is-not-a-valid-header',
|
|
@@ -109,14 +109,14 @@ describe('resolveInterfaceLocale — tier 3 (Accept-Language)', () => {
|
|
|
109
109
|
})
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
describe('
|
|
112
|
+
describe('resolveAdminLocale — tier 4 (default)', () => {
|
|
113
113
|
it('returns defaultLocale when every other tier produces nothing', () => {
|
|
114
|
-
const out =
|
|
114
|
+
const out = resolveAdminLocale({ locales, defaultLocale })
|
|
115
115
|
expect(out).toBe('en')
|
|
116
116
|
})
|
|
117
117
|
|
|
118
118
|
it('returns defaultLocale when every signal is empty', () => {
|
|
119
|
-
const out =
|
|
119
|
+
const out = resolveAdminLocale({
|
|
120
120
|
locales,
|
|
121
121
|
defaultLocale,
|
|
122
122
|
preferred: null,
|
package/src/resolve.ts
CHANGED
|
@@ -29,10 +29,10 @@ import Negotiator from 'negotiator'
|
|
|
29
29
|
|
|
30
30
|
import type { LocaleCode } from './types.js'
|
|
31
31
|
|
|
32
|
-
export interface
|
|
33
|
-
/** Permitted locale set — from `i18n.
|
|
32
|
+
export interface ResolveAdminLocaleOptions {
|
|
33
|
+
/** Permitted locale set — from `i18n.admin.locales`. */
|
|
34
34
|
locales: readonly LocaleCode[]
|
|
35
|
-
/** Last-resort fallback — from `i18n.
|
|
35
|
+
/** Last-resort fallback — from `i18n.admin.defaultLocale`. */
|
|
36
36
|
defaultLocale: LocaleCode
|
|
37
37
|
/** `admin_users.preferred_locale` for the authenticated request, if any. */
|
|
38
38
|
preferred?: LocaleCode | null
|
|
@@ -42,7 +42,7 @@ export interface ResolveInterfaceLocaleOptions {
|
|
|
42
42
|
acceptLanguage?: string | null
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function
|
|
45
|
+
export function resolveAdminLocale(options: ResolveAdminLocaleOptions): LocaleCode {
|
|
46
46
|
const { locales, defaultLocale, preferred, cookie, acceptLanguage } = options
|
|
47
47
|
|
|
48
48
|
// Tier 1 — admin user preference.
|
package/src/types.ts
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Locale code — kept as a plain `string` so the package stays open to
|
|
11
11
|
* any BCP 47 tag (`en`, `pt-BR`, `zh-Hans-CN`). The host's
|
|
12
|
-
* `i18n.
|
|
12
|
+
* `i18n.admin.locales` config is the canonical allow-list at
|
|
13
13
|
* runtime; the resolver / validator narrow against it.
|
|
14
14
|
*/
|
|
15
15
|
export type LocaleCode = string
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Lightweight locale definition used by `<LanguageMenu>` to render the
|
|
19
|
-
* dropdown. The `code` matches a string in `i18n.
|
|
19
|
+
* dropdown. The `code` matches a string in `i18n.admin.locales`;
|
|
20
20
|
* the `nativeName` is what the user sees ("English", "Español",
|
|
21
21
|
* "Français", "Deutsch", "日本語").
|
|
22
22
|
*/
|