@nxgt/i18n 1.0.1 → 1.0.3

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
@@ -1,39 +1,72 @@
1
1
  # @nxgt/i18n
2
2
 
3
- Backend-side i18n primitives shared by the Hono/GraphQL services: a tiny ICU
4
- translator (`createTranslator`, `translate`, `getLanguage`), the language
5
- constants (`LANGUAGE_KEY`, `FALLBACK_LANGUAGE`, `SUPPORTED_LANGUAGES`), the
6
- `LocaleKey`/`FlatObject`/`Path` types, and the base resource bundle
7
- (`common`, `errors`, `validation`, `zod`) that every service merges into its own.
3
+ Message catalogues and ICU formatting (`intl-messageformat`) for every service,
4
+ plus the `LocaleKey` type that `@nxgt/shared-exceptions` uses to type the
5
+ message of a thrown error.
8
6
 
9
- ## Scope: backend only
7
+ An error message is a **key**, not a sentence: `CustomException` carries
8
+ `'users.errors.not-found'` and the boundary that renders it decides the
9
+ language. That is why `LocaleKey` lives this low in the layering.
10
10
 
11
- The UI apps (`apps/oauth/oauth`, `apps/oauth/oauth-admin`,
12
- `apps/storex/storex-ui`) do **not** use this package. They share their i18n
13
- mechanism through `@nxgt/material/i18n`, which adds the React side this package
14
- deliberately has none of: an `I18nProvider` holding the language in one context
15
- so a `changeLanguage()` re-renders every consumer, and `createTypedTranslation`
16
- to build each app's typed `useTranslation()`. Do not add a frontend consumer
17
- here — extend `@nxgt/material/i18n` instead.
11
+ ## Install
18
12
 
19
- ## Consumers
13
+ ```bash
14
+ bun add @nxgt/i18n
15
+ ```
20
16
 
21
- - `apps/gateway`
22
- - `apps/oauth/oauth-api`, `apps/storex/storex-api`
23
- - `apps/services/core`, `apps/services/locations`, `apps/services/notifications`
24
- - `packages/shared-exceptions`, `packages/shared-hono`, `packages/shared-mongo`,
25
- `packages/shared-storage`
17
+ Public on npmjs; no token needed to install. TypeScript is a peer, pinned to
18
+ `^6.0.3` across every `@nxgt/*` package — the set is unsatisfiable if one of
19
+ them widens it.
26
20
 
27
- A service builds its own translator over its own resources merged with this
28
- package's base bundle:
21
+ ## Usage
29
22
 
30
23
  ```ts
31
- import { createTranslator, resources as shared } from '@nxgt/i18n';
24
+ import {
25
+ createTranslator,
26
+ getLanguage,
27
+ translate,
28
+ FALLBACK_LANGUAGE,
29
+ SUPPORTED_LANGUAGES,
30
+ LANGUAGE_KEY,
31
+ } from '@nxgt/i18n';
32
32
 
33
- export const resources = {
34
- en: { ...en, ...shared.en },
35
- fr: { ...fr, ...shared.fr },
36
- };
33
+ translate('errors.not-found');
37
34
 
38
- export const translate = createTranslator<LocaleKey>(resources);
35
+ const t = createTranslator(myCatalogues, getLanguage);
36
+ t('users.greeting', { name });
39
37
  ```
38
+
39
+ `translate` is bound to this package's catalogues (`en` and `fr`, under
40
+ `errors`, `common`, `validation`, `zod`). Pass your own catalogues to
41
+ `createTranslator` when a service has messages this package does not. A missing
42
+ key returns the key itself, not a throw.
43
+
44
+ `LocaleKey` is `keyof` the flattened English catalogue — that is the type
45
+ `CustomException.message` uses. `Language` is `'en' | 'fr'`. `resources`,
46
+ `en` and `fr` are the catalogues themselves. Shared keys include
47
+ `errors.not-found`, `errors.unauthenticated`, `errors.insufficient-permissions`
48
+ and `errors.service-unavailable`.
49
+
50
+ ## Where the language comes from
51
+
52
+ `getLanguage()` asks, most specific first:
53
+
54
+ 1. the Hono request context (`LANGUAGE_KEY`, `'language'`)
55
+ 2. `localStorage.language`, when that object exists
56
+ 3. `FALLBACK_LANGUAGE` (`'en'`)
57
+
58
+ The two consuming monorepos had forked exactly this — one read the request, the
59
+ other read `localStorage` — and neither could run where the other did. Asking
60
+ both serves either without a caller changing anything. Pass a provider (a
61
+ function or a `Language` literal) to `createTranslator` if you want one source
62
+ and not the other.
63
+
64
+ `SUPPORTED_LANGUAGES` is derived from the catalogue keys, not typed out.
65
+
66
+ ## Things that bite
67
+
68
+ - **Messages are keys.** Rendering at the throw site (or translating in a
69
+ service) freezes the language of whoever threw, not of whoever reads.
70
+ - **ICU formatting failures are swallowed** (`console.error`) and the
71
+ unformatted message is returned. A bad `{name}` in a catalogue is a log
72
+ line, not a 500.
package/dist/index.js CHANGED
@@ -474,10 +474,10 @@ function getLanguage() {
474
474
  }
475
475
  return FALLBACK_LANGUAGE;
476
476
  }
477
- function createTranslator(resources2 = {}, localeProvider = getLanguage) {
477
+ function createTranslator(resources = {}, localeProvider = getLanguage) {
478
478
  return (key, context = undefined, provideLanguage = localeProvider) => {
479
479
  const language = typeof provideLanguage === "function" ? provideLanguage() : provideLanguage;
480
- let message = _.get(resources2[language], key) ?? key;
480
+ let message = _.get(resources[language], key) ?? key;
481
481
  try {
482
482
  message = new IntlMessageFormat(message, language).format(context);
483
483
  } catch (e) {
@@ -497,5 +497,5 @@ export {
497
497
  translate
498
498
  };
499
499
 
500
- //# debugId=629CBE18E4850F3E64756E2164756E21
500
+ //# debugId=9824428B699DC6DF64756E2164756E21
501
501
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -8,7 +8,7 @@
8
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
9
  "import { tryGetContext } from 'hono/context-storage';\nimport { IntlMessageFormat } from 'intl-messageformat';\nimport _ from 'lodash';\nimport { FALLBACK_LANGUAGE, LANGUAGE_KEY, SUPPORTED_LANGUAGES } from './consts';\nimport { resources } from './resources';\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.\n *\n * The two repositories had forked exactly this: sellix-monorepo read\n * `localStorage`, nxgt-federation read the Hono request context, and neither\n * could run where the other did — federation's fell back to `'en'` outside a\n * request, sellix's knew nothing about one. Asking both, most specific first,\n * serves either without a caller changing anything.\n *\n * `createTranslator` still takes a provider, so a caller that wants one source\n * and not the other passes it.\n */\nexport function getLanguage(): Language {\n\tconst fromRequest: unknown = tryGetContext()?.get(LANGUAGE_KEY as never);\n\tif (\n\t\ttypeof fromRequest === 'string' &&\n\t\tSUPPORTED_LANGUAGES.includes(fromRequest as Language)\n\t) {\n\t\treturn fromRequest as Language;\n\t}\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"
10
10
  ],
11
- "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,YAAY;AAAA,EACxB;AAAA,EACA;AACD;;;ACHO,IAAM,eAAe;AAErB,IAAM,sBAAsB,OAAO,KAAK,SAAS;AAEjD,IAAM,oBAA8B;;ACP3C;AACA;AACA;AAsBO,SAAS,WAAW,GAAa;AAAA,EACvC,MAAM,cAAuB,cAAc,GAAG,IAAI,YAAqB;AAAA,EACvE,IACC,OAAO,gBAAgB,YACvB,oBAAoB,SAAS,WAAuB,GACnD;AAAA,IACD,OAAO;AAAA,EACR;AAAA,EACA,IAAI,OAAO,iBAAiB,aAAa;AAAA,IACxC,MAAM,SAAS,aAAa,QAAQ,YAAY;AAAA,IAChD,IAAI,oBAAoB,SAAS,MAAkB,GAAG;AAAA,MACrD,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAGD,SAAS,gBAA8C,CAC7D,aAAiC,CAAC,GAClC,iBAAmC,aAClC;AAAA,EACD,OAAO,CACN,KACA,UAA8B,WAC9B,kBAAoC,mBACxB;AAAA,IACZ,MAAM,WACL,OAAO,oBAAoB,aACxB,gBAAgB,IAChB;AAAA,IACJ,IAAI,UAAkB,EAAE,IAAI,WAAU,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,YAAY,iBAAiB,SAAS;",
12
- "debugId": "629CBE18E4850F3E64756E2164756E21",
11
+ "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,YAAY;AAAA,EACxB;AAAA,EACA;AACD;;;ACHO,IAAM,eAAe;AAErB,IAAM,sBAAsB,OAAO,KAAK,SAAS;AAEjD,IAAM,oBAA8B;;ACP3C;AACA;AACA;AAsBO,SAAS,WAAW,GAAa;AAAA,EACvC,MAAM,cAAuB,cAAc,GAAG,IAAI,YAAqB;AAAA,EACvE,IACC,OAAO,gBAAgB,YACvB,oBAAoB,SAAS,WAAuB,GACnD;AAAA,IACD,OAAO;AAAA,EACR;AAAA,EACA,IAAI,OAAO,iBAAiB,aAAa;AAAA,IACxC,MAAM,SAAS,aAAa,QAAQ,YAAY;AAAA,IAChD,IAAI,oBAAoB,SAAS,MAAkB,GAAG;AAAA,MACrD,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAGD,SAAS,gBAA8C,CAC7D,YAAiC,CAAC,GAClC,iBAAmC,aAClC;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,YAAY,iBAAiB,SAAS;",
12
+ "debugId": "9824428B699DC6DF64756E2164756E21",
13
13
  "names": []
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/i18n",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",