@ultimat3/i18n 20.2.1 → 22.0.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 +9 -0
- package/package.json +2 -2
- package/src/catalogs/en.json +2 -1
- package/src/locales.ts +17 -6
package/README.md
CHANGED
|
@@ -117,6 +117,15 @@ X_CATALOG_MISSING_KEYS: catalog is incomplete
|
|
|
117
117
|
| `X_CATALOG_MISSING_KEYS` | a shipped locale lacks a key the source uses |
|
|
118
118
|
| `X_CATALOG_INVALID` | non-string leaf, bad key segment, or a dotted/nested collision |
|
|
119
119
|
|
|
120
|
+
### Error classes
|
|
121
|
+
|
|
122
|
+
Every error class `src/index.ts` exports, for `instanceof` inside one process. Across a wire or
|
|
123
|
+
a job boundary the class is gone and the `code` is what survives — match on that.
|
|
124
|
+
|
|
125
|
+
| Class | Code | Declared in |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `I18nError` | `X_LOCALE_UNSUPPORTED`, `X_CATALOG_MISSING_KEYS`, `X_CATALOG_INVALID`, `X_CATALOG_UNREGISTERED` | `src/errors.ts` |
|
|
128
|
+
|
|
120
129
|
## Why it exists
|
|
121
130
|
|
|
122
131
|
Retrofitting i18n means touching every string in the app. Structuring for many locales on
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/i18n",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "22.0.0",
|
|
4
4
|
"description": "Dependency-free translator, catalog flattening, locale negotiation and loud missing-key rendering",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"test": "bun test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ultimat3/core": "
|
|
38
|
+
"@ultimat3/core": "22.0.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/catalogs/en.json
CHANGED
|
@@ -194,7 +194,8 @@
|
|
|
194
194
|
"error": {
|
|
195
195
|
"action-failed": "The action threw before it finished.",
|
|
196
196
|
"confirmation-required": "This is destructive: echo the confirmation token first.",
|
|
197
|
-
"invalid-input": "Some fields did not pass the entity's own schema."
|
|
197
|
+
"invalid-input": "Some fields did not pass the entity's own schema.",
|
|
198
|
+
"operation-not-offered": "This resource does not offer that operation."
|
|
198
199
|
},
|
|
199
200
|
"form": {
|
|
200
201
|
"cancel": "Cancel",
|
package/src/locales.ts
CHANGED
|
@@ -50,8 +50,18 @@ export const SUPPORTED_LOCALES: readonly Locale[] = [
|
|
|
50
50
|
export { type Direction, directionOf, isRtl } from '@ultimat3/core';
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* The registered spelling of `lower`, compared case-insensitively. A registered `pt-BR` used to
|
|
54
|
+
* be compared EXACTLY against a request tag this function had just lowercased, so `pt-BR` from a
|
|
55
|
+
* query, a cookie or a header never matched it and every such request fell back.
|
|
56
|
+
*/
|
|
57
|
+
function registered(lower: string, supported: readonly Locale[]): Locale | undefined {
|
|
58
|
+
return supported.find((locale) => locale.toLowerCase().replace(/_/g, '-') === lower);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Strip region, match case-insensitively, answer the REGISTERED spelling, fall back to the default.
|
|
63
|
+
* `pt-BR` → `pt-BR` when that tag is registered, else `pt`; `ZH-Hant-TW` → `zh-hant` (or
|
|
64
|
+
* `zh-Hant`, as registered) when that tag is supported, else `zh`.
|
|
55
65
|
*/
|
|
56
66
|
export function normalizeLocale(
|
|
57
67
|
tag?: string | null,
|
|
@@ -61,17 +71,18 @@ export function normalizeLocale(
|
|
|
61
71
|
if (!tag) return fallback;
|
|
62
72
|
const lower = tag.trim().toLowerCase().replace(/_/g, '-');
|
|
63
73
|
if (lower.length === 0 || lower === '*') return fallback;
|
|
64
|
-
|
|
74
|
+
const whole = registered(lower, supported);
|
|
75
|
+
if (whole !== undefined) return whole;
|
|
65
76
|
|
|
66
77
|
const segments = lower.split('-');
|
|
67
78
|
const primary = segments[0] ?? '';
|
|
68
79
|
// `lang-script` is a distinct written form (zh-hant vs zh-hans) — try it before dropping to `lang`.
|
|
69
80
|
const script = segments[1];
|
|
70
81
|
if (script !== undefined && script.length === 4) {
|
|
71
|
-
const withScript = `${primary}-${script}
|
|
72
|
-
if (
|
|
82
|
+
const withScript = registered(`${primary}-${script}`, supported);
|
|
83
|
+
if (withScript !== undefined) return withScript;
|
|
73
84
|
}
|
|
74
|
-
return
|
|
85
|
+
return registered(primary, supported) ?? fallback;
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
/** Same as `normalizeLocale` but loud: use where an unknown locale is a caller bug. */
|