@orderly.network/i18n 3.0.4-alpha.3 → 3.0.4-alpha.4

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  **This guide** documents props, effects, and loading strategies. For **end-to-end copy-paste recipes** (Vite `import.meta.glob`, Next.js/webpack, HTTP `public/`, sync maps, URL sync), use [Examples](./examples.md).
4
4
 
5
- **Overview:** `LocaleProvider` composes `**LanguageProvider`** (language list, optional HTTP `Backend`, change callbacks) and `**I18nextProvider`** from react-i18next. All of it uses the package’s **singleton `i18n`instance**. The default namespace is`**translation`** (`defaultNS`); see [Package exports](./exports.md).
5
+ **Overview:** `LocaleProvider` composes `LanguageProvider` (language list, optional HTTP `Backend`, change callbacks) and `I18nextProvider` from react-i18next. All of it uses the package’s singleton `i18n` instance. The default namespace is `translation` (`defaultNS`); see [Package exports](./exports.md).
6
6
 
7
7
  Follow the steps below to integrate localization in your app with the Orderly SDK.
8
8
 
@@ -36,10 +36,10 @@ These props are defined on `LocaleProvider` (see `localeProvider.tsx`):
36
36
  | Prop | Description |
37
37
  | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
38
  | `locale` | Optional **controlled** locale. When set and different from `i18n.language`, a `useEffect` calls `i18n.changeLanguage(locale)`. |
39
- | `resource` | Flat messages for `**defaultNS`** (`translation`). Used only when `**resources`is not set**; requires`locale`. Calls `i18n.addResourceBundle(locale, defaultNS, resource, true, true)`. |
39
+ | `resource` | Flat messages for `defaultNS` (`translation`). Used only when `resources` is not set; requires `locale`. Calls `i18n.addResourceBundle(locale, defaultNS, resource, true, true)`. |
40
40
  | `resources` | Static **Resources** map or **AsyncResources**. When set, **registerResources** runs in a `useEffect` (see **Behavior**). Takes precedence over `locale` + `resource`. Same contract as `ExternalLocaleProvider` / `useRegisterExternalResources`. |
41
41
 
42
- **Async loader and `ns`:** The `AsyncResources` type is `(lang, ns) => Promise<Record<string, string>>`. When loading goes through `**registerResources`** (from `LocaleProvider` or `useRegisterExternalResources`), the implementation calls `**await resources(localeCode, defaultNS)`** — the second argument is **always** `defaultNS` (`translation`), not an arbitrary namespace. Use the parameter if you build URLs; for multiple i18n namespaces, use the i18n API directly.
42
+ **Async loader and `ns`:** The `AsyncResources` type is `(lang, ns) => Promise<Record<string, string>>`. When loading goes through `registerResources` (from `LocaleProvider`, `ExternalLocaleProvider`, or `useRegisterExternalResources`), the implementation calls `await resources(localeCode, defaultNS)` — the second argument is **always** `defaultNS` (`translation`), not an arbitrary namespace. Use the parameter if you build URLs; for multiple i18n namespaces, use the i18n API directly.
43
43
 
44
44
  ### Inherited from `LanguageProvider`
45
45
 
@@ -49,7 +49,7 @@ Pass these through `LocaleProvider` like any other `LanguageProvider` prop:
49
49
  | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
50
  | `backend` | `BackendOptions`: `{ loadPath }` where `loadPath(lang, ns)` returns a URL `string`, `string[]`, or `undefined` for the HTTP `Backend`. Those URLs must resolve to real files (e.g. under `public/`); the package does **not** copy `dist/locales` into your app — sync manually or via a script / hook — see [HTTP backend](./examples.md#http-backend). |
51
51
  | `languages` | Full `Language[]` for the switcher; when set as an array, replaces `defaultLanguages`. |
52
- | `supportedLanguages` | Subset of `LocaleCode[]`; builds the list from `**defaultLanguages**` entries. |
52
+ | `supportedLanguages` | Subset of `LocaleCode[]`; builds the list from `defaultLanguages` entries. |
53
53
  | `onLanguageBeforeChanged` | `(lang) => Promise<void>`. **Runs first**; then the internal `Backend` loads the next language (`loadLanguage(lang, defaultNS)`). Use for prep work before HTTP loads. |
54
54
  | `onLanguageChanged` | `(lang) => Promise<void>` — notification on the language-change path. |
55
55
  | `convertDetectedLanguage` | `(browserLang: string) => LocaleCode` — optional mapping from the detector to your supported codes. |
@@ -57,16 +57,16 @@ Pass these through `LocaleProvider` like any other `LanguageProvider` prop:
57
57
 
58
58
  ### Behavior (effects)
59
59
 
60
- - `**resources` set:\*\* `registerResources(resources, locale ?? currentLocale)` runs when `locale`, `resource`, `resources`, or the current locale from `useLocaleCode` changes. Static maps register every locale entry; async loaders fetch for the active locale code.
61
- - `**resources` unset** and `**resource`+`locale`:** merges the flat bundle for that locale into `defaultNS`.
62
- - `**locale` prop:\*\* separate effect — if `locale` is set and differs from `i18n.language`, `i18n.changeLanguage(locale)` runs.
60
+ - **`resources` set:** `registerResources(resources, locale ?? currentLocale)` runs when `locale`, `resource`, `resources`, or the current locale from `useLocaleCode` changes. Static maps register every locale entry; async loaders fetch for the active locale code.
61
+ - **`resources` unset and `resource` + `locale`:** merges the flat bundle for that locale into `defaultNS`.
62
+ - **`locale` prop:** separate effect — if `locale` is set and differs from `i18n.language`, `i18n.changeLanguage(locale)` runs.
63
63
 
64
- Prefer **one** primary loading approach per app (**HTTP `backend`** vs **static/async `resources`** vs `**locale` + `resource**`) to avoid overlapping bundles. You can pass `**resources` on `LocaleProvider**` instead of `ExternalLocaleProvider` — same registration path. Use `**useRegisterExternalResources**` to avoid an extra wrapper (stable `resources` reference recommended).
64
+ Prefer **one** primary loading approach per app (**HTTP `backend`** vs **static/async `resources`** vs **`locale` + `resource`**) to avoid overlapping bundles. You can pass `resources` on `LocaleProvider` instead of `ExternalLocaleProvider` — same registration path. Use `useRegisterExternalResources` to avoid an extra wrapper (stable `resources` reference recommended).
65
65
 
66
66
  ### Loading strategies (quick reference)
67
67
 
68
68
  - **HTTP:** `backend={{ loadPath }}` — load JSON from URLs (e.g. files under `public/`). You must **place** those JSON files on disk (or CDN): **manually** copy from `node_modules/.../i18n/dist/locales`, or run a **copy script** / **Husky** hook (`npm run copyLocales`, etc.) as in [HTTP backend](./examples.md#http-backend).
69
- - **Bundled:** `resources` as a static map or **`AsyncResources`**. Recipes: [Async resources (Vite)](./examples.md#async-resources-vite) · [Async resources (Next.js and webpack)](./examples.md#async-resources-nextjs-and-webpack) · [Sync resources](./examples.md#sync-resources).
69
+ - **Bundled:** `resources` as a static map or `AsyncResources`. Recipes: [Async resources (Vite)](./examples.md#async-resources-vite) · [Async resources (Next.js and webpack)](./examples.md#async-resources-nextjs-and-webpack) · [Sync resources](./examples.md#sync-resources).
70
70
  - **Controlled single bundle:** `locale` + `resource` when you inject one flat table for one language.
71
71
  - **Host / external bundles:** `ExternalLocaleProvider` or `useRegisterExternalResources` — same `Resources` / `AsyncResources` as `LocaleProvider.resources`.
72
72
 
@@ -78,7 +78,7 @@ Prefer **one** primary loading approach per app (**HTTP `backend`** vs **static/
78
78
 
79
79
  ### Supported locales
80
80
 
81
- We currently support **17** locales. The table order matches `**defaultLanguages`\*\* in the package (`constant`).
81
+ We currently support **17** locales. The table order matches `defaultLanguages` in the package (`constant`).
82
82
 
83
83
  | Locale Code | Language |
84
84
  | ----------- | ------------------- |
@@ -109,7 +109,7 @@ We currently support **17** locales. The table order matches `**defaultLanguages
109
109
 
110
110
  You can translate SDK strings and add strings for your own UI.
111
111
 
112
- - Use the `**extend.`\*\* key prefix for custom keys so they stay distinct from built-in keys (and align with tooling such as `separateJson` in the [CLI](./cli.md)).
112
+ - Use the `extend.` key prefix for custom keys so they stay distinct from built-in keys (and align with tooling such as `separateJson` in the [CLI](./cli.md)).
113
113
 
114
114
  ```json
115
115
  {
@@ -119,44 +119,93 @@ You can translate SDK strings and add strings for your own UI.
119
119
 
120
120
  ## 4. Integrate external resources
121
121
 
122
- Use this when strings live outside this package (another bundle, CDN, or host app). `**LocaleProvider**` with `**resources**`, `**ExternalLocaleProvider**`, and `**useRegisterExternalResources**` all call the same `**registerResources**` helper.
122
+ Use this when strings live outside this package (another bundle, CDN, or host app). `LocaleProvider` with `resources`, `ExternalLocaleProvider`, and `useRegisterExternalResources` all call the same `registerResources` helper.
123
123
 
124
- The snippets below are **minimal** (wrapper vs hook). [Examples](./examples.md) uses **`LocaleProvider` + `resources`** with full app wiring (e.g. merge SDK + extend, provider tree)—use that for production-shaped code.
124
+ ### Host app vs external package
125
125
 
126
- For **Vite**, bundling SDK locales with your `extend` JSON via `**AsyncResources`\*\* is the recommended setup — see [Async resources (Vite)](./examples.md#async-resources-vite).
126
+ Pick the integration point based on who owns the React root:
127
+
128
+ - **Host apps:** mount `LocaleProvider` once at the app/orderly root. Pass `backend`, `resources`, or `locale` + `resource` there. For Vite/Next/webpack app-level recipes, see [Examples](./examples.md).
129
+ - **External SDK/plugin packages:** export a package-local provider based on `ExternalLocaleProvider`. Do not mount another root `@orderly.network/i18n` `LocaleProvider` inside the package.
130
+
131
+ For host apps, bundling SDK locales with your `extend` JSON via `LocaleProvider` + `resources` is still the recommended setup when you own the provider tree. See [Async resources (Vite)](./examples.md#async-resources-vite), [Async resources (Next.js and webpack)](./examples.md#async-resources-nextjs-and-webpack), and [Sync resources](./examples.md#sync-resources).
127
132
 
128
133
  ### `ExternalLocaleProvider`
129
134
 
130
135
  - **Async:** `(lang, ns) => Promise<Record<string, string>>` — invoked when the locale changes (same `ns` behavior as above when used through `registerResources`).
131
136
  - **Sync:** static `Resources` map; all listed locales are registered on mount.
132
137
 
133
- Async example:
138
+ Async external resources are also registered as preloaders while their provider is mounted. Calls to the package singleton `i18n.changeLanguage(locale)` wait for those mounted external resources to load the target locale before the language is switched. This keeps plugin/host extension bundles lazy by locale while ensuring the first render after the language switch already has the target external messages registered.
139
+
140
+ #### Async example (recommended for external packages)
141
+
142
+ For external SDK packages or plugin packages, ship a provider like
143
+ `package-template/src/i18n/provider.tsx`: preload the default English messages,
144
+ load non-English JSON chunks lazily with statically analyzable imports, and wrap
145
+ the package subtree with `ExternalLocaleProvider`.
146
+
147
+ This keeps the host app in charge of the root `LocaleProvider` while your package contributes its own translation resources to the shared singleton `i18n` instance. Use explicit per-locale dynamic imports so webpack/Next can resolve locale JSON chunks reliably. Vite accepts the same pattern.
134
148
 
135
149
  ```tsx
150
+ import { FC, PropsWithChildren } from "react";
136
151
  import {
137
152
  AsyncResources,
138
153
  ExternalLocaleProvider,
154
+ importLocaleJsonModule,
139
155
  LocaleCode,
140
- LocaleProvider,
156
+ LocaleEnum,
157
+ preloadDefaultResource,
158
+ type LocaleJsonModule,
141
159
  } from "@orderly.network/i18n";
160
+ import { LocaleMessages } from "./module";
161
+
162
+ type LocaleJsonLoader = () => Promise<LocaleJsonModule>;
163
+
164
+ const localeJsonLoaders: Record<LocaleEnum, LocaleJsonLoader | undefined> = {
165
+ [LocaleEnum.en]: undefined,
166
+ [LocaleEnum.zh]: () => import("./locales/zh.json"),
167
+ [LocaleEnum.ja]: () => import("./locales/ja.json"),
168
+ [LocaleEnum.es]: () => import("./locales/es.json"),
169
+ [LocaleEnum.ko]: () => import("./locales/ko.json"),
170
+ [LocaleEnum.vi]: () => import("./locales/vi.json"),
171
+ [LocaleEnum.de]: () => import("./locales/de.json"),
172
+ [LocaleEnum.fr]: () => import("./locales/fr.json"),
173
+ [LocaleEnum.ru]: () => import("./locales/ru.json"),
174
+ [LocaleEnum.id]: () => import("./locales/id.json"),
175
+ [LocaleEnum.tr]: () => import("./locales/tr.json"),
176
+ [LocaleEnum.it]: () => import("./locales/it.json"),
177
+ [LocaleEnum.pt]: () => import("./locales/pt.json"),
178
+ [LocaleEnum.uk]: () => import("./locales/uk.json"),
179
+ [LocaleEnum.pl]: () => import("./locales/pl.json"),
180
+ [LocaleEnum.nl]: () => import("./locales/nl.json"),
181
+ [LocaleEnum.tc]: () => import("./locales/tc.json"),
182
+ };
142
183
 
143
- const resources: AsyncResources = async (lang: LocaleCode) => {
144
- return import(`./locales/${lang}.json`).then(
145
- (res) => res.default as Record<string, string>,
146
- );
184
+ // Seed fallback messages before async locale chunks load to avoid flashing i18n keys.
185
+ preloadDefaultResource(LocaleMessages);
186
+
187
+ const resources: AsyncResources = (lang: LocaleCode, _ns: string) => {
188
+ if (lang === LocaleEnum.en) {
189
+ return Promise.resolve(LocaleMessages);
190
+ }
191
+
192
+ const loader = localeJsonLoaders[lang as LocaleEnum];
193
+ return importLocaleJsonModule(loader);
147
194
  };
148
195
 
149
- export function App() {
196
+ export const LocaleProvider: FC<PropsWithChildren> = (props) => {
150
197
  return (
151
- <LocaleProvider>
152
- <ExternalLocaleProvider resources={resources}>
153
- <YourApp />
154
- </ExternalLocaleProvider>
155
- </LocaleProvider>
198
+ <ExternalLocaleProvider resources={resources}>
199
+ {props.children}
200
+ </ExternalLocaleProvider>
156
201
  );
157
- }
202
+ };
158
203
  ```
159
204
 
205
+ The exported `LocaleProvider` above is your package-local provider. It is not the root `LocaleProvider` from `@orderly.network/i18n`; rename it if your package needs to expose both.
206
+
207
+ `LocaleMessages` should be the package's English/default message map, and `./locales/<locale>.json` should contain the translated package messages for that locale. If your package supports only a subset of languages, keep the full `Record<LocaleEnum, ...>` shape but leave unsupported loaders as `undefined`; `importLocaleJsonModule(undefined)` safely returns an empty resource object.
208
+
160
209
  Sync example:
161
210
 
162
211
  ```tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderly.network/i18n",
3
- "version": "3.0.4-alpha.3",
3
+ "version": "3.0.4-alpha.4",
4
4
  "description": "Internationalization for orderly sdk",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -50,7 +50,7 @@
50
50
  "react-dom": "^18.2.0",
51
51
  "tsup": "^8.5.1",
52
52
  "typescript": "^5.1.6",
53
- "tsconfig": "1.0.4-alpha.3"
53
+ "tsconfig": "1.0.4-alpha.4"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": ">=18",