@nxgt/i18n 1.0.4 → 2.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 CHANGED
@@ -51,10 +51,31 @@ and `errors.service-unavailable`.
51
51
 
52
52
  `getLanguage()` asks, most specific first:
53
53
 
54
- 1. the Hono request context (`LANGUAGE_KEY`, `'language'`)
54
+ 1. every registered **language source**, in the order registered
55
55
  2. `localStorage.language`, when that object exists
56
56
  3. `FALLBACK_LANGUAGE` (`'en'`)
57
57
 
58
+ A source is a function answering the current request's language, or nothing.
59
+ This package knows no server; the server says where its request keeps the
60
+ language:
61
+
62
+ ```ts
63
+ import { registerLanguageSource } from '@nxgt/i18n';
64
+
65
+ const remove = registerLanguageSource(() => currentRequest()?.language);
66
+ ```
67
+
68
+ | server | its source |
69
+ | --- | --- |
70
+ | Hono | `@nxgt/shared-hono` registers `honoLanguageSource` when imported: the `language` variable `languageDetector()` sets, read through `contextStorage()` |
71
+ | GraphQL Yoga on Hono | `@nxgt/shared-graphql`'s `createYogaHono()` registers the same |
72
+ | alxia | `@alxia/i18n`'s `createI18n()` registers the request's language |
73
+
74
+ A source that answers nothing, a language no catalogue has, or throws is
75
+ skipped. Registering one twice keeps one. The registry is on `globalThis`, so
76
+ two copies of this package in one app — one a dependency of
77
+ `@nxgt/shared-hono`, one the app's own — share it.
78
+
58
79
  The two consuming monorepos had forked exactly this — one read the request, the
59
80
  other read `localStorage` — and neither could run where the other did. Asking
60
81
  both serves either without a caller changing anything. Pass a provider (a
@@ -63,8 +84,29 @@ and not the other.
63
84
 
64
85
  `SUPPORTED_LANGUAGES` is derived from the catalogue keys, not typed out.
65
86
 
87
+ ### Since 2.0
88
+
89
+ Until 2.0, `getLanguage()` read the Hono request context itself, through
90
+ `hono/context-storage`: every consumer depended on Hono, and a server on
91
+ anything else got the fallback. The Hono part lives in `@nxgt/shared-hono`
92
+ now, with the `ContextVariableMap` augmentation that types `c.get('language')`.
93
+ An app that imports `@nxgt/shared-hono` sees no difference; one that used
94
+ `@nxgt/i18n` beside Hono without it calls `useHonoLanguage()` from
95
+ `@nxgt/shared-hono` once, or registers its own source.
96
+
97
+ ## In a browser bundle
98
+
99
+ Nothing to change: `import { translate } from '@nxgt/i18n'` is the same import
100
+ whether the code runs in Node, Bun, or a bundler that resolves the `browser`
101
+ export condition (Vite, Nuxt's client build). Since 2.0 nothing in the package
102
+ is Node-only, and the `browser` entry is the same code; a client app registers
103
+ a source as a server does, or relies on `localStorage`.
104
+
66
105
  ## Things that bite
67
106
 
107
+ - **A server's language is a registered source.** Without one, every
108
+ request is translated in `localStorage`'s language — none, on a server — or
109
+ the fallback.
68
110
  - **Messages are keys.** Rendering at the throw site (or translating in a
69
111
  service) freezes the language of whoever threw, not of whoever reads.
70
112
  - **ICU formatting failures are swallowed** (`console.error`) and the
@@ -0,0 +1,524 @@
1
+ // src/resources/en/common.json
2
+ var common_default = {
3
+ mail: {
4
+ common: {
5
+ thanks: "Thanks,",
6
+ greeting: "Hello {name},",
7
+ copyright: "© {date} Strange. All rights reserved."
8
+ },
9
+ templates: {
10
+ verification: {
11
+ message: "We''re happy to have you on board! Use your verification code in order to activate your account. Verification code:",
12
+ verify: "Verify email",
13
+ notes: "We are not responsible if you decide to share you're account credentials. Make sure to keep secret."
14
+ }
15
+ }
16
+ },
17
+ "opening-hours": {
18
+ errors: {
19
+ "invalid-start-time-format": "The opening time format is invalid. Please use the HH:mm format.",
20
+ "invalid-end-time-format": "The closing time format is invalid. Please use the HH:mm format."
21
+ }
22
+ }
23
+ };
24
+ // src/resources/en/errors.json
25
+ var errors_default = {
26
+ "not-found": "Could not find the requested resource.",
27
+ unauthenticated: "You're not authenticated",
28
+ "duplicate-key": "There was a duplicate key error. Please, check data and try again",
29
+ "something-went-wrong": "Something went wrong. Please, check and try again.",
30
+ "insufficient-permissions": "You don't have required permissions to access this resource. Please, contact your administrator.",
31
+ "cast-failed": "Incorrect value for the field ''{field}''. Please, check the value and try again.",
32
+ "server-connection-failed": "Failed to connect to the database server. Please, contact your administrator.",
33
+ forbidden: "You don't have necessary permission to access this resource. Please, contact your administrator.",
34
+ "optimistic-lock-failed": "The resource has been modified by another user. Please refresh and try again.",
35
+ "internal-server-error": "Internal server error. Please, contact your administrator.",
36
+ "validation-failed": "Validation failed for one or more fields. Please, check the data and try again.",
37
+ "address-blocked": "The access is blocked for address ``{address}``.",
38
+ "could-not-use-first-and-last-together": "You cannot use ''first'' and ''last'' parameters together in pagination.",
39
+ "invalid-cursor": "The provided cursor is invalid. Please, check and try again.",
40
+ "invalid-status-transition": "Cannot change status from ''{from}'' to ''{to}''.",
41
+ "service-unavailable": "A service this request depends on is not answering. Nothing was changed; please try again in a moment.",
42
+ "graphql-parse-failed": "Failed to parse the GraphQL query. Check your query syntax.",
43
+ "graphql-validation-failed": "The GraphQL query is invalid. Please check the operation and variables.",
44
+ "persisted-query-not-found": "Persisted query not found. The query hash is unknown.",
45
+ "persisted-query-not-supported": "Persisted queries are not supported on this server.",
46
+ "bad-user-input": "Invalid input provided. Please check your data and try again.",
47
+ "operation-resolution-failure": "Failed to resolve the GraphQL operation.",
48
+ "bad-request": "The request is invalid or malformed. Please verify the request format."
49
+ };
50
+ // src/resources/en/validation.json
51
+ var validation_default = {
52
+ errors: {
53
+ "invalid-account-number": "The provided value is not a valid account number.",
54
+ "invalid-big-int": "The value must be a valid BigInt.",
55
+ "invalid-country-code": "The value must be a valid two-letter ISO country code.",
56
+ "invalid-country-name": "The value must be a valid country name.",
57
+ "invalid-cuid": "The value must be a valid CUID.",
58
+ "invalid-currency": "The value must be a valid ISO 4217 currency code.",
59
+ "invalid-date": "The value must be a valid date in the format YYYY-MM-DD.",
60
+ "invalid-date-time": "The value must be a valid ISO 8601 date-time string.",
61
+ "invalid-duration": "The value must be a valid duration in ISO 8601 format.",
62
+ "invalid-email-address": "The value must be a valid email address.",
63
+ "invalid-geo-json": "The value must be a valid GeoJSON object.",
64
+ "invalid-guid": "The value must be a valid GUID.",
65
+ "invalid-hex-color-code": "The value must be a valid hexadecimal color code.",
66
+ "invalid-hexadecimal": "The value must be a valid hexadecimal string.",
67
+ "invalid-hsl": "The value must be a valid HSL (Hue, Saturation, Lightness) color format.",
68
+ "invalid-hsla": "The value must be a valid HSLA (Hue, Saturation, Lightness, Alpha) color format.",
69
+ "invalid-iban": "The value must be a valid International Bank Account Number (IBAN).",
70
+ "invalid-ip": "The value must be a valid IP address.",
71
+ "invalid-ipv4": "The value must be a valid IPv4 address.",
72
+ "invalid-ipv6": "The value must be a valid IPv6 address.",
73
+ "invalid-isbn": "The value must be a valid ISBN (International Standard Book Number).",
74
+ "invalid-json": "The value must be valid JSON.",
75
+ "invalid-json-object": "The value must be a valid JSON object.",
76
+ "invalid-jwt": "The value must be a valid JSON Web Token (JWT).",
77
+ "invalid-latitude": "The value must be a valid latitude coordinate.",
78
+ "invalid-locale": "The value must be a valid locale code.",
79
+ "invalid-longitude": "The value must be a valid longitude coordinate.",
80
+ "invalid-long": "The value must be a valid long integer.",
81
+ "invalid-mac": "The value must be a valid MAC address.",
82
+ "invalid-negative-float": "The value must be a negative floating-point number.",
83
+ "invalid-negative-int": "The value must be a negative integer.",
84
+ "invalid-non-empty-string": "The value must be a non-empty string.",
85
+ "invalid-non-negative-float": "The value must be a floating-point number equal to 0 or more.",
86
+ "invalid-non-negative-int": "The value must be a integer equal to 0 or more.",
87
+ "invalid-non-positive-float": "The value must be a floating-point number equal to 0 or less.",
88
+ "invalid-non-positive-int": "The value must be a integer equal to 0 or less.",
89
+ "invalid-object-id": "The value must be a valid MongoDB ObjectID.",
90
+ "invalid-phone-number": "The value must be a valid phone number.",
91
+ "invalid-port": "The value must be a valid port number.",
92
+ "invalid-positive-float": "The value must be a positive floating-point number.",
93
+ "invalid-positive-int": "The value must be a positive integer.",
94
+ "invalid-postal-code": "The value must be a valid postal code.",
95
+ "invalid-rgb": "The value must be a valid RGB (Red, Green, Blue) color format.",
96
+ "invalid-rgba": "The value must be a valid RGBA (Red, Green, Blue, Alpha) color format.",
97
+ "invalid-sessn": "The value must be a valid session identifier.",
98
+ "invalid-time": "The value must be a valid time in the format HH:mm:ss.",
99
+ "invalid-timestamp": "The value must be a valid timestamp.",
100
+ "invalid-url": "The value must be a valid URL.",
101
+ "invalid-us-currency": "The value must be a valid US currency format.",
102
+ "invalid-utc-offset": "The value must be a valid UTC offset.",
103
+ "invalid-uuid": "The value must be a valid UUID.",
104
+ "invalid-void": "The value must be null or undefined.",
105
+ min: "The minimum value for the field ''{path}'' must be greater than or equal to {min}.",
106
+ max: "The maximum value for the field ''{path}'' must be less than or equal to {max}.",
107
+ "min-length": "The minimum length for the field ''{path}'' must be at least {minLength} characters.",
108
+ "max-length": "The maximum length for the field ''{path}'' must not exceed {maxLength} characters.",
109
+ range: "The value for the field ''{path}'' must be between {min} and {max}.",
110
+ size: "The length of the field ''{path}'' must be between {min} and {max}.",
111
+ past: "The value for the field ''{path}'' must be a valid date before {limit}.",
112
+ future: "The value for the field ''{path}'' must be a valid date after {limit}.",
113
+ email: "The value for the field ''{path}'' must be {numberOfDomains, plural, =0 {a valid email address} =1 {an email address within the domain ''{domains}''} other {an email address within one of the following domains: [{domains}]}.",
114
+ ip: "The value for the field ''{path}'' must be a valid IP address.",
115
+ ipv4: "The value for the field ''{path}'' must be a valid IPv4 address.",
116
+ ipv6: "The value for the field ''{path}'' must be a valid IPv6 address.",
117
+ mac: "The value for the field ''{path}'' must be a valid MAC address.",
118
+ url: "The value for the field ''{path}'' must be {numberOfDomains, plural, =0 {a valid URL} =1 {a URL within the domain ''{domains}''} other {a URL within one of the following domains: [{domains}]}.",
119
+ uuid: "The value for the field ''{path}'' must be a valid UUID.",
120
+ guid: "The value for the field ''{path}'' must be a valid GUID.",
121
+ ssn: "The value for the field ''{path}'' must be a valid social security number(SSN).",
122
+ phone: "The value for the field ''{path}'' must be a valid phone number.",
123
+ street: "The value for the field ''{path}'' must be a valid street address.",
124
+ "zip-code": "The value for the field ''{path}'' must be a valid ZIP code.",
125
+ "object-id": "The value for the field ''{path}'' must be a 24-character hexadecimal string.",
126
+ pattern: "Invalid value for the field ''{path}'. The value must match the pattern: ''{pattern}''.",
127
+ "not-blank": "The field ''{path}'' must not be blank.",
128
+ "not-empty": "The field ''{path}'' must not be empty.",
129
+ positive: "The value for the field ''{path}'' must be a positive number.",
130
+ negative: "The value for the field ''{path}'' must be a negative number.",
131
+ "no-scalars": "Invalid type: ''{type}'' is not a scalar type.",
132
+ "not-null": "The value for {length, plural, =0 {the field} other {each item of the field} ''{path}'' should not be null.",
133
+ required: "The value for the field ''{path}'' is required.",
134
+ time: "The value for the field ''{path}'' must be a valid time in the format HH:mm."
135
+ }
136
+ };
137
+ // src/resources/en/zod.json
138
+ var zod_default = {
139
+ required: "This field is required.",
140
+ invalid_type: {
141
+ string: "Une chaîne de texte est attendue.",
142
+ number: "Un nombre est attendu.",
143
+ boolean: "Un booléen est attendu.",
144
+ object: "Un objet est attendu.",
145
+ array: "Une liste est attendue.",
146
+ bigint: "Un nombre entier très grand est attendu.",
147
+ date: "Une date valide est attendue.",
148
+ symbol: "Un symbole est attendu.",
149
+ undefined: "Cette valeur est manquante.",
150
+ null: "La valeur doit être nulle.",
151
+ function: "Une fonction est attendue.",
152
+ enum: "L’option choisie est invalide.",
153
+ default: "Type invalide."
154
+ },
155
+ invalid_value_single: "This value is not allowed.",
156
+ invalid_value_multiple: "This option is not valid.",
157
+ too_big_inclusive: "This value is too large: it must be at most {maximum} {unit}.",
158
+ too_big_exclusive: "This value is too large: it must be less than {maximum} {unit}.",
159
+ too_big_no_unit_inclusive: "This value is too large: it must be at most {maximum}.",
160
+ too_big_no_unit_exclusive: "This value is too large: it must be less than {maximum}.",
161
+ too_small_inclusive: "This value is too small: it must be at least {minimum} {unit}.",
162
+ too_small_exclusive: "This value is too small: it must be more than {minimum} {unit}.",
163
+ too_small_no_unit_inclusive: "This value is too small: it must be at least {minimum}.",
164
+ too_small_no_unit_exclusive: "This value is too small: it must be more than {minimum}.",
165
+ invalid_format: {
166
+ starts_with: 'This text must start with "{prefix}".',
167
+ ends_with: 'This text must end with "{suffix}".',
168
+ includes: 'This text must include "{includes}".',
169
+ regex: "This value doesn’t match the required format.",
170
+ default: "Invalid {noun}."
171
+ },
172
+ not_multiple_of: "This number must be a multiple of {divisor}.",
173
+ unrecognized_keys_single: "Unknown field: {keys}.",
174
+ unrecognized_keys_multiple: "Unknown fields: {keys}.",
175
+ invalid_key: "This key is not valid in {origin}.",
176
+ invalid_union: "This value does not match any of the allowed formats.",
177
+ invalid_element: "This field contains an invalid value in {origin}.",
178
+ default: "This value is not valid.",
179
+ nouns: {
180
+ regex: "value",
181
+ email: "email address",
182
+ url: "URL",
183
+ emoji: "emoji",
184
+ uuid: "UUID",
185
+ uuidv4: "UUIDv4",
186
+ uuidv6: "UUIDv6",
187
+ nanoid: "nanoid",
188
+ guid: "GUID",
189
+ cuid: "cuid",
190
+ cuid2: "cuid2",
191
+ ulid: "ULID",
192
+ xid: "XID",
193
+ ksuid: "KSUID",
194
+ datetime: "date and time",
195
+ date: "date",
196
+ time: "time",
197
+ duration: "duration",
198
+ ipv4: "IPv4 address",
199
+ ipv6: "IPv6 address",
200
+ mac: "MAC address",
201
+ cidrv4: "IPv4 range",
202
+ cidrv6: "IPv6 range",
203
+ base64: "base64 string",
204
+ base64url: "base64url string",
205
+ json_string: "JSON string",
206
+ e164: "phone number",
207
+ jwt: "JWT",
208
+ template_literal: "value"
209
+ },
210
+ sizing: {
211
+ string: { unit: "characters", verb: "contain" },
212
+ file: { unit: "bytes", verb: "contain" },
213
+ array: { unit: "items", verb: "contain" },
214
+ set: { unit: "items", verb: "contain" }
215
+ }
216
+ };
217
+
218
+ // src/resources/en/index.ts
219
+ var en = {
220
+ ...common_default,
221
+ errors: errors_default,
222
+ validation: validation_default,
223
+ zod: zod_default
224
+ };
225
+ // src/resources/fr/common.json
226
+ var common_default2 = {
227
+ mail: {
228
+ common: {
229
+ thanks: "Merci,",
230
+ greeting: "Bonjour {name},",
231
+ copyright: "© {date} Strange. Tous droits réservés."
232
+ },
233
+ templates: {
234
+ verification: {
235
+ message: "Nous sommes heureux de vous compter parmi nous ! Utilisez votre code de vérification pour activer votre compte. Code de vérification :",
236
+ verify: "Vérifier l''e-mail",
237
+ notes: "Nous ne sommes pas responsables si vous décidez de partager les informations d''identification de votre compte. Veillez à garder le secret."
238
+ }
239
+ }
240
+ },
241
+ "opening-hours": {
242
+ errors: {
243
+ "invalid-start-time-format": "Le format de l''heure d''ouverture est invalide. Utilisez le format HH:mm.",
244
+ "invalid-end-time-format": "Le format de l''heure de fermeture est invalide. Utilisez le format HH:mm."
245
+ }
246
+ }
247
+ };
248
+ // src/resources/fr/errors.json
249
+ var errors_default2 = {
250
+ "not-found": "Impossible de trouver la ressource demandée.",
251
+ unauthenticated: "Vous n'êtes pas authentifié",
252
+ "duplicate-key": "Il y a eu une erreur de clé dupliquée. Veuillez vérifier les données et réessayer",
253
+ "something-went-wrong": "Quelque chose n'a pas fonctionné. Veuillez vérifier et réessayer.",
254
+ "insufficient-permissions": "Vous n'avez pas les permissions nécessaires pour accéder à cette ressource. Veuillez contacter votre administrateur.",
255
+ "cast-failed": "Valeur incorrecte pour le champ ''{field}''. Veuillez vérifier la valeur et réessayer.",
256
+ "server-connection-failed": "Échec de la connexion au serveur de base de données. Veuillez contacter votre administrateur.",
257
+ forbidden: "Vous n'avez pas la permission nécessaire pour accéder à cette ressource. Veuillez contacter votre administrateur.",
258
+ "optimistic-lock-failed": "La ressource a été modifiée par un autre utilisateur. Veuillez actualiser et réessayer.",
259
+ "internal-server-error": "Erreur interne du serveur. Veuillez contacter votre administrateur.",
260
+ "validation-failed": "La validation a échoué pour un ou plusieurs champs. Veuillez vérifier les données et réessayer.",
261
+ "address-blocked": "L'accès est bloqué pour l'adresse ``{address}``.",
262
+ "could-not-use-first-and-last-together": "Vous ne pouvez pas utiliser les paramètres ''first'' et ''last'' ensemble dans la pagination.",
263
+ "invalid-cursor": "Le curseur fourni est invalide. Veuillez vérifier et réessayer.",
264
+ "invalid-status-transition": "Impossible de changer le statut de ''{from}'' à ''{to}''.",
265
+ "service-unavailable": "Un service dont dépend cette requête ne répond pas. Rien n'a été modifié ; réessayez dans un instant.",
266
+ "graphql-parse-failed": "Échec de l'analyse de la requête GraphQL. Vérifiez la syntaxe de votre requête.",
267
+ "graphql-validation-failed": "La requête GraphQL est invalide. Veuillez vérifier l'opération et les variables.",
268
+ "persisted-query-not-found": "Requête persistante non trouvée. Le hash de la requête est inconnu.",
269
+ "persisted-query-not-supported": "Les requêtes persistantes ne sont pas prises en charge sur ce serveur.",
270
+ "bad-user-input": "Données saisies invalides. Veuillez vérifier vos informations et réessayer.",
271
+ "operation-resolution-failure": "Échec de la résolution de l'opération GraphQL.",
272
+ "bad-request": "La requête est invalide ou mal formée. Veuillez vérifier le format de la requête."
273
+ };
274
+ // src/resources/fr/validation.json
275
+ var validation_default2 = {
276
+ errors: {
277
+ "invalid-account-number": "La valeur fournie n'est pas un numéro de compte valide.",
278
+ "invalid-big-int": "La valeur doit être un BigInt valide.",
279
+ "invalid-country-code": "La valeur doit être un code pays ISO à deux lettres valide.",
280
+ "invalid-country-name": "La valeur doit être un nom de pays valide.",
281
+ "invalid-cuid": "La valeur doit être un CUID valide.",
282
+ "invalid-currency": "La valeur doit être un code de devise ISO 4217 valide.",
283
+ "invalid-date": "La valeur doit être une date valide au format AAAA-MM-JJ.",
284
+ "invalid-date-time": "La valeur doit être une date et une heure valides au format ISO 8601.",
285
+ "invalid-duration": "La valeur doit être une durée valide au format ISO 8601.",
286
+ "invalid-email-address": "La valeur doit être une adresse e-mail valide.",
287
+ "invalid-geo-json": "La valeur doit être un objet GeoJSON valide.",
288
+ "invalid-guid": "La valeur doit être un GUID valide.",
289
+ "invalid-hex-color-code": "La valeur doit être un code couleur hexadécimal valide.",
290
+ "invalid-hexadecimal": "La valeur doit être une chaîne hexadécimale valide.",
291
+ "invalid-hsl": "La valeur doit être un format de couleur HSL (Teinte, Saturation, Luminosité) valide.",
292
+ "invalid-hsla": "La valeur doit être un format de couleur HSLA (Teinte, Saturation, Luminosité, Alpha) valide.",
293
+ "invalid-iban": "La valeur doit être un numéro IBAN (International Bank Account Number) valide.",
294
+ "invalid-ip": "La valeur doit être une adresse IP valide.",
295
+ "invalid-ipv4": "La valeur doit être une adresse IPv4 valide.",
296
+ "invalid-ipv6": "La valeur doit être une adresse IPv6 valide.",
297
+ "invalid-isbn": "La valeur doit être un ISBN (International Standard Book Number) valide.",
298
+ "invalid-json": "La valeur doit être un JSON valide.",
299
+ "invalid-json-object": "La valeur doit être un objet JSON valide.",
300
+ "invalid-jwt": "La valeur doit être un jeton JSON Web Token (JWT) valide.",
301
+ "invalid-latitude": "La valeur doit être une coordonnée de latitude valide.",
302
+ "invalid-locale": "La valeur doit être un code de langue valide.",
303
+ "invalid-longitude": "La valeur doit être une coordonnée de longitude valide.",
304
+ "invalid-long": "La valeur doit être un entier long valide.",
305
+ "invalid-mac": "La valeur doit être une adresse MAC valide.",
306
+ "invalid-negative-float": "La valeur doit être un nombre flottant négatif.",
307
+ "invalid-negative-int": "La valeur doit être un entier négatif.",
308
+ "invalid-non-empty-string": "La valeur doit être une chaîne non vide.",
309
+ "invalid-non-negative-float": "La valeur doit être un nombre à virgule flottante égal ou supérieur à 0.",
310
+ "invalid-non-negative-int": "La valeur doit être un entier égal ou supérieur à 0.",
311
+ "invalid-non-positive-float": "La valeur doit être un nombre à virgule flottante égal ou inférieur à 0.",
312
+ "invalid-non-positive-int": "La valeur doit être un entier égal ou inférieur à 0.",
313
+ "invalid-object-id": "La valeur doit être un ObjectID MongoDB valide.",
314
+ "invalid-phone-number": "La valeur doit être un numéro de téléphone valide.",
315
+ "invalid-port": "La valeur doit être un numéro de port valide.",
316
+ "invalid-positive-float": "La valeur doit être un nombre flottant positif.",
317
+ "invalid-positive-int": "La valeur doit être un entier positif.",
318
+ "invalid-postal-code": "La valeur doit être un code postal valide.",
319
+ "invalid-rgb": "La valeur doit être un format de couleur RGB (Rouge, Vert, Bleu) valide.",
320
+ "invalid-rgba": "La valeur doit être un format de couleur RGBA (Rouge, Vert, Bleu, Alpha) valide.",
321
+ "invalid-sessn": "La valeur doit être un identifiant de session valide.",
322
+ "invalid-time": "La valeur doit être une heure valide au format HH:mm:ss.",
323
+ "invalid-timestamp": "La valeur doit être un horodatage valide.",
324
+ "invalid-url": "La valeur doit être une URL valide.",
325
+ "invalid-us-currency": "La valeur doit être un format de devise US valide.",
326
+ "invalid-utc-offset": "La valeur doit être un décalage UTC valide.",
327
+ "invalid-uuid": "La valeur doit être un UUID valide.",
328
+ "invalid-void": "La valeur doit être nulle ou non définie.",
329
+ min: "La valeur minimale pour le champ ''{path}'' doit être supérieure ou égale à {min}.",
330
+ max: "La valeur maximale pour le champ ''{path}'' doit être inférieure ou égale à {max}.",
331
+ "min-length": "La longueur minimale pour le champ {path} doit être d'au moins {minLength} caractères.",
332
+ "max-length": "La longueur maximale pour le champ ''{path}'' ne doit pas dépasser {maxLength} caractères.",
333
+ range: "La valeur pour le champ ''{path}'' doit être comprise entre {min} et {max}.",
334
+ size: "La longueur du champ ''{path}'' doit être comprise entre {min} et {max}.",
335
+ past: "La valeur pour le champ ''{path}'' doit être une date valide antérieure à {limit}.",
336
+ future: "La valeur pour le champ ''{path}'' doit être une date valide postérieure à {limit}.",
337
+ email: "La valeur pour le champ ''{path}'' doit être {numberOfDomains, plural, =0 {une adresse électronique valide} =1 {une adresse électronique dans le domaine ''{domains}'' } other {une adresse électronique dans l'un des domaines suivants : [{domains}]}.",
338
+ ip: "La valeur pour le champ ''{path}'' doit être une adresse IP valide.",
339
+ ipv4: "La valeur pour le champ ''{path}'' doit être une adresse IPv4 valide.",
340
+ ipv6: "La valeur pour le champ ''{path}'' doit être une adresse IPv6 valide.",
341
+ mac: "La valeur pour le champ ''{path}'' doit être une adresse MAC valide.",
342
+ url: "La valeur pour le champ ''{path}'' doit être {numberOfDomains, plural, =0 {une URL valide} =1 {une URL dans le domaine ''{domains}'' } other {une URL dans l'un des domaines suivants : [{domains}]}.",
343
+ uuid: "La valeur pour le champ ''{path}'' doit être un UUID valide.",
344
+ guid: "La valeur pour le champ ''{path}'' doit être un GUID valide.",
345
+ ssn: "La valeur pour le champ ''{path}'' doit être un numéro de sécurité social valide.",
346
+ phone: "La valeur pour le champ ''{path}'' doit être un numéro de téléphone valide.",
347
+ street: "La valeur pour le champ ''{path}'' doit être une adresse postale valide.",
348
+ "zip-code": "La valeur pour le champ ''{path}'' doit être un code postal valide.",
349
+ "object-id": "La valeur pour le champ ''{path}'' doit être une chaîne hexadécimale de 24 caractères.",
350
+ pattern: "Valeur invalide pour le champ ''{path}'' . La valeur doit correspondre au motif : ''{pattern}'' .",
351
+ "not-blank": "{length, plural, =0 {Le champ} other {Chaque élément du champ} ''{path}'' ne doit pas être vide.",
352
+ "not-empty": "{length, plural, =0 {Le champ} other {Chaque élément du champ} ''{path}'' ne doit pas être vide.",
353
+ positive: "La valeur pour le champ ''{path}'' doit être un nombre positif.",
354
+ negative: "La valeur pour le champ ''{path}'' doit être un nombre négatif.",
355
+ "no-scalars": "Type invalide : ''{type}'' n'est pas un type scalaire.",
356
+ "not-null": "La valeur pour le champ ''{path}'' ne doit pas être nulle.",
357
+ required: "La valeur pour le champ ''{path}'' est requise.",
358
+ time: "La valeur pour le champ ''{path}'' doit être une heure valide au format HH:mm."
359
+ }
360
+ };
361
+ // src/resources/fr/zod.json
362
+ var zod_default2 = {
363
+ required: "Ce champ est requis.",
364
+ invalid_type: {
365
+ string: "Une chaîne de texte est attendue.",
366
+ number: "Un nombre est attendu.",
367
+ boolean: "Un booléen est attendu.",
368
+ object: "Un objet est attendu.",
369
+ array: "Une liste est attendue.",
370
+ bigint: "Un nombre entier très grand est attendu.",
371
+ date: "Une date valide est attendue.",
372
+ symbol: "Un symbole est attendu.",
373
+ undefined: "Cette valeur est manquante.",
374
+ null: "La valeur doit être nulle.",
375
+ function: "Une fonction est attendue.",
376
+ enum: "L’option choisie est invalide.",
377
+ default: "Type invalide."
378
+ },
379
+ invalid_value_single: "Cette valeur n’est pas autorisée.",
380
+ invalid_value_multiple: "Cette option n’est pas valide.",
381
+ too_big_inclusive: "Cette valeur est trop grande : elle doit être au maximum {maximum} {unit}.",
382
+ too_big_exclusive: "Cette valeur est trop grande : elle doit être inférieure à {maximum} {unit}.",
383
+ too_big_no_unit_inclusive: "Cette valeur est trop grande : elle doit être au maximum {maximum}.",
384
+ too_big_no_unit_exclusive: "Cette valeur est trop grande : elle doit être inférieure à {maximum}.",
385
+ too_small_inclusive: "Cette valeur est trop petite : elle doit être au minimum {minimum} {unit}.",
386
+ too_small_exclusive: "Cette valeur est trop petite : elle doit être supérieure à {minimum} {unit}.",
387
+ too_small_no_unit_inclusive: "Cette valeur est trop petite : elle doit être au minimum {minimum}.",
388
+ too_small_no_unit_exclusive: "Cette valeur est trop petite : elle doit être supérieure à {minimum}.",
389
+ invalid_format: {
390
+ starts_with: 'Le texte doit commencer par "{prefix}".',
391
+ ends_with: 'Le texte doit se terminer par "{suffix}".',
392
+ includes: 'Le texte doit contenir "{includes}".',
393
+ regex: "Ce format n’est pas valide.",
394
+ default: "{noun} invalide."
395
+ },
396
+ not_multiple_of: "Ce nombre doit être un multiple de {divisor}.",
397
+ unrecognized_keys_single: "Champ inconnu : {keys}.",
398
+ unrecognized_keys_multiple: "Champs inconnus : {keys}.",
399
+ invalid_key: "Ce champ n’est pas valide dans {origin}.",
400
+ invalid_union: "Cette valeur ne correspond à aucun des formats acceptés.",
401
+ invalid_element: "Ce champ contient une valeur invalide dans {origin}.",
402
+ default: "Cette valeur n’est pas valide.",
403
+ nouns: {
404
+ regex: "valeur",
405
+ email: "adresse e-mail",
406
+ url: "URL",
407
+ emoji: "emoji",
408
+ uuid: "UUID",
409
+ uuidv4: "UUIDv4",
410
+ uuidv6: "UUIDv6",
411
+ nanoid: "nanoid",
412
+ guid: "GUID",
413
+ cuid: "cuid",
414
+ cuid2: "cuid2",
415
+ ulid: "ULID",
416
+ xid: "XID",
417
+ ksuid: "KSUID",
418
+ datetime: "date et heure",
419
+ date: "date",
420
+ time: "heure",
421
+ duration: "durée",
422
+ ipv4: "adresse IPv4",
423
+ ipv6: "adresse IPv6",
424
+ mac: "adresse MAC",
425
+ cidrv4: "plage IPv4",
426
+ cidrv6: "plage IPv6",
427
+ base64: "chaîne base64",
428
+ base64url: "chaîne base64url",
429
+ json_string: "chaîne JSON",
430
+ e164: "numéro de téléphone",
431
+ jwt: "JWT",
432
+ template_literal: "valeur"
433
+ },
434
+ sizing: {
435
+ string: { unit: "caractères", verb: "contenir" },
436
+ file: { unit: "octets", verb: "contenir" },
437
+ array: { unit: "éléments", verb: "contenir" },
438
+ set: { unit: "éléments", verb: "contenir" }
439
+ }
440
+ };
441
+
442
+ // src/resources/fr/index.ts
443
+ var fr = {
444
+ ...common_default2,
445
+ errors: errors_default2,
446
+ validation: validation_default2,
447
+ zod: zod_default2
448
+ };
449
+
450
+ // src/resources/index.ts
451
+ var resources2 = {
452
+ en,
453
+ fr
454
+ };
455
+
456
+ // src/consts.ts
457
+ var LANGUAGE_KEY2 = "language";
458
+ var SUPPORTED_LANGUAGES2 = Object.keys(resources2);
459
+ var FALLBACK_LANGUAGE2 = "en";
460
+
461
+ // src/sources.ts
462
+ var REGISTRY = Symbol.for("@nxgt/i18n/language-sources");
463
+ function registry() {
464
+ const global = globalThis;
465
+ global[REGISTRY] ??= [];
466
+ return global[REGISTRY];
467
+ }
468
+ function registerLanguageSource2(source) {
469
+ const sources = registry();
470
+ if (!sources.includes(source))
471
+ sources.push(source);
472
+ return () => {
473
+ const index = sources.indexOf(source);
474
+ if (index !== -1)
475
+ sources.splice(index, 1);
476
+ };
477
+ }
478
+ function languageFromSources2() {
479
+ for (const source of registry()) {
480
+ let answer;
481
+ try {
482
+ answer = source();
483
+ } catch {
484
+ continue;
485
+ }
486
+ if (SUPPORTED_LANGUAGES2.includes(answer)) {
487
+ return answer;
488
+ }
489
+ }
490
+ return;
491
+ }
492
+
493
+ // src/i18n.ts
494
+ import { IntlMessageFormat } from "intl-messageformat";
495
+ import _ from "lodash";
496
+ function getLanguage2() {
497
+ const fromSource = languageFromSources2();
498
+ if (fromSource !== undefined)
499
+ return fromSource;
500
+ if (typeof localStorage !== "undefined") {
501
+ const stored = localStorage.getItem(LANGUAGE_KEY2);
502
+ if (SUPPORTED_LANGUAGES2.includes(stored)) {
503
+ return stored;
504
+ }
505
+ }
506
+ return FALLBACK_LANGUAGE2;
507
+ }
508
+ function createTranslator2(resources = {}, localeProvider = getLanguage2) {
509
+ return (key, context = undefined, provideLanguage = localeProvider) => {
510
+ const language = typeof provideLanguage === "function" ? provideLanguage() : provideLanguage;
511
+ let message = _.get(resources[language], key) ?? key;
512
+ try {
513
+ message = new IntlMessageFormat(message, language).format(context);
514
+ } catch (e) {
515
+ console.error(e);
516
+ }
517
+ return message;
518
+ };
519
+ }
520
+ var translate2 = createTranslator2(resources2);
521
+ export { resources2, LANGUAGE_KEY2, SUPPORTED_LANGUAGES2, FALLBACK_LANGUAGE2, registerLanguageSource2, languageFromSources2, getLanguage2, createTranslator2, translate2 };
522
+
523
+ //# debugId=104509F5A9E2FCCE64756E2164756E21
524
+ //# sourceMappingURL=index-vknxbeh1.js.map
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/resources/en/index.ts", "../src/resources/fr/index.ts", "../src/resources/index.ts", "../src/consts.ts", "../src/sources.ts", "../src/i18n.ts"],
4
+ "sourcesContent": [
5
+ "import common from './common.json';\nimport errors from './errors.json';\nimport validation from './validation.json';\nimport zod from './zod.json';\n\nexport const en = {\n\t...common,\n\terrors,\n\tvalidation,\n\tzod,\n};\n",
6
+ "import common from './common.json';\nimport errors from './errors.json';\nimport validation from './validation.json';\nimport zod from './zod.json';\n\nexport const fr = {\n\t...common,\n\terrors,\n\tvalidation,\n\tzod,\n};\n",
7
+ "import { en } from './en';\nimport { fr } from './fr';\n\nexport const resources = {\n\ten,\n\tfr,\n};\n",
8
+ "import { resources } from './resources';\nimport type { Language } from './types';\n\nexport const LANGUAGE_KEY = 'language';\n\nexport const SUPPORTED_LANGUAGES = Object.keys(resources) as Language[];\n\nexport const FALLBACK_LANGUAGE: Language = 'en';\n",
9
+ "import { SUPPORTED_LANGUAGES } from './consts';\nimport type { Language } from './types';\n\n/**\n * Where a language may come from: a server's request context, a framework's\n * state. It answers the request's language, or nothing — `undefined`, `null`,\n * a language no catalogue has — and the next source is asked.\n */\nexport type LanguageSource = () => string | null | undefined;\n\n/**\n * One registry for the process, on `globalThis`: an app can hold two copies\n * of this package — one a dependency of `@nxgt/shared-hono`, one its own —\n * and a source registered through either must answer both.\n */\nconst REGISTRY = Symbol.for('@nxgt/i18n/language-sources');\n\nfunction registry(): LanguageSource[] {\n\tconst global = globalThis as { [REGISTRY]?: LanguageSource[] };\n\tglobal[REGISTRY] ??= [];\n\treturn global[REGISTRY];\n}\n\n/**\n * Adds a source `getLanguage()` asks, after those registered before it.\n * Registering one twice keeps one. Answers the function that removes it.\n *\n * This package knows no server: `@nxgt/shared-hono` registers the Hono\n * request context's `language`, `@alxia/i18n` the alxia request's.\n */\nexport function registerLanguageSource(source: LanguageSource): () => void {\n\tconst sources = registry();\n\tif (!sources.includes(source)) sources.push(source);\n\treturn () => {\n\t\tconst index = sources.indexOf(source);\n\t\tif (index !== -1) sources.splice(index, 1);\n\t};\n}\n\n/**\n * The first supported language a registered source answers. A source that\n * throws is skipped: a language is never worth a failed request.\n */\nexport function languageFromSources(): Language | undefined {\n\tfor (const source of registry()) {\n\t\tlet answer: string | null | undefined;\n\t\ttry {\n\t\t\tanswer = source();\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (SUPPORTED_LANGUAGES.includes(answer as Language)) {\n\t\t\treturn answer as Language;\n\t\t}\n\t}\n\treturn undefined;\n}\n",
10
+ "import { IntlMessageFormat } from 'intl-messageformat';\nimport _ from 'lodash';\nimport { FALLBACK_LANGUAGE, LANGUAGE_KEY, SUPPORTED_LANGUAGES } from './consts';\nimport { resources } from './resources';\nimport { languageFromSources } from './sources';\nimport type {\n\tLanguage,\n\tLanguageProvider,\n\tLocaleKey,\n\tTranslationContext,\n} from './types';\n\n/**\n * Where the language comes from when the caller does not say, most specific\n * first:\n *\n * 1. a registered source — `registerLanguageSource()`: a server's request\n * context, which `@nxgt/shared-hono` registers for Hono and\n * `@alxia/i18n` for alxia;\n * 2. `localStorage`, when that object exists;\n * 3. the fallback language.\n *\n * Until 2.0 this read Hono's request context itself, through\n * `hono/context-storage`, which tied every consumer — a browser bundle, an\n * alxia server — to Hono. The Hono part lives in `@nxgt/shared-hono` now.\n *\n * `createTranslator` still takes a provider, so a caller that wants one source\n * and not the others passes it.\n */\nexport function getLanguage(): Language {\n\tconst fromSource = languageFromSources();\n\tif (fromSource !== undefined) return fromSource;\n\tif (typeof localStorage !== 'undefined') {\n\t\tconst stored = localStorage.getItem(LANGUAGE_KEY);\n\t\tif (SUPPORTED_LANGUAGES.includes(stored as Language)) {\n\t\t\treturn stored as Language;\n\t\t}\n\t}\n\treturn FALLBACK_LANGUAGE;\n}\n\nexport function createTranslator<K extends string = LocaleKey>(\n\tresources: Record<string, any> = {},\n\tlocaleProvider: LanguageProvider = getLanguage,\n) {\n\treturn (\n\t\tkey: K,\n\t\tcontext: TranslationContext = undefined,\n\t\tprovideLanguage: LanguageProvider = localeProvider,\n\t): string => {\n\t\tconst language =\n\t\t\ttypeof provideLanguage === 'function'\n\t\t\t\t? provideLanguage()\n\t\t\t\t: provideLanguage;\n\t\tlet message: string = _.get(resources[language], key) ?? key;\n\t\ttry {\n\t\t\tmessage = new IntlMessageFormat(message, language).format(context);\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t}\n\n\t\treturn message;\n\t};\n}\n\nexport const translate = createTranslator(resources);\n"
11
+ ],
12
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKO,IAAM,KAAK;AAAA,KACd;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACLO,IAAM,KAAK;AAAA,KACd;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACD;;;ACPO,IAAM,aAAY;AAAA,EACxB;AAAA,EACA;AACD;;;ACHO,IAAM,gBAAe;AAErB,IAAM,uBAAsB,OAAO,KAAK,UAAS;AAEjD,IAAM,qBAA8B;;;ACQ3C,IAAM,WAAW,OAAO,IAAI,6BAA6B;AAEzD,SAAS,QAAQ,GAAqB;AAAA,EACrC,MAAM,SAAS;AAAA,EACf,OAAO,cAAc,CAAC;AAAA,EACtB,OAAO,OAAO;AAAA;AAUR,SAAS,uBAAsB,CAAC,QAAoC;AAAA,EAC1E,MAAM,UAAU,SAAS;AAAA,EACzB,IAAI,CAAC,QAAQ,SAAS,MAAM;AAAA,IAAG,QAAQ,KAAK,MAAM;AAAA,EAClD,OAAO,MAAM;AAAA,IACZ,MAAM,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IACpC,IAAI,UAAU;AAAA,MAAI,QAAQ,OAAO,OAAO,CAAC;AAAA;AAAA;AAQpC,SAAS,oBAAmB,GAAyB;AAAA,EAC3D,WAAW,UAAU,SAAS,GAAG;AAAA,IAChC,IAAI;AAAA,IACJ,IAAI;AAAA,MACH,SAAS,OAAO;AAAA,MACf,MAAM;AAAA,MACP;AAAA;AAAA,IAED,IAAI,qBAAoB,SAAS,MAAkB,GAAG;AAAA,MACrD,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA;AAAA;;;ACvDD;AACA;AA4BO,SAAS,YAAW,GAAa;AAAA,EACvC,MAAM,aAAa,qBAAoB;AAAA,EACvC,IAAI,eAAe;AAAA,IAAW,OAAO;AAAA,EACrC,IAAI,OAAO,iBAAiB,aAAa;AAAA,IACxC,MAAM,SAAS,aAAa,QAAQ,aAAY;AAAA,IAChD,IAAI,qBAAoB,SAAS,MAAkB,GAAG;AAAA,MACrD,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAGD,SAAS,iBAA8C,CAC7D,YAAiC,CAAC,GAClC,iBAAmC,cAClC;AAAA,EACD,OAAO,CACN,KACA,UAA8B,WAC9B,kBAAoC,mBACxB;AAAA,IACZ,MAAM,WACL,OAAO,oBAAoB,aACxB,gBAAgB,IAChB;AAAA,IACJ,IAAI,UAAkB,EAAE,IAAI,UAAU,WAAW,GAAG,KAAK;AAAA,IACzD,IAAI;AAAA,MACH,UAAU,IAAI,kBAAkB,SAAS,QAAQ,EAAE,OAAO,OAAO;AAAA,MAChE,OAAO,GAAG;AAAA,MACX,QAAQ,MAAM,CAAC;AAAA;AAAA,IAGhB,OAAO;AAAA;AAAA;AAIF,IAAM,aAAY,kBAAiB,UAAS;",
13
+ "debugId": "104509F5A9E2FCCE64756E2164756E21",
14
+ "names": []
15
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The `browser` condition's build of `./i18n`. They were two files while
3
+ * `./i18n` imported `hono/context-storage`, which pulls in `node:async_hooks`;
4
+ * since 2.0 neither imports anything Node-only, and a browser registers its
5
+ * own language sources as a server does. The entry point stays, so a bundler
6
+ * that resolves the `browser` condition keeps resolving it.
7
+ */
8
+ export * from './i18n';
9
+ //# sourceMappingURL=i18n.browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.browser.d.ts","sourceRoot":"","sources":["../src/i18n.browser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,QAAQ,CAAC"}