@nimpl/i18n 0.0.0-experimental-47e75fe → 0.0.0-experimental-504b302

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.
@@ -7,5 +7,5 @@ type ClientTranslationProps = {
7
7
  query?: I18nOptions["query"];
8
8
  removeUnusedQueries?: I18nOptions["removeUnusedQueries"];
9
9
  };
10
- declare const ClientTranslation: React.FC<ClientTranslationProps>;
11
- export default ClientTranslation;
10
+ export declare const ClientTranslation: React.FC<ClientTranslationProps>;
11
+ export {};
package/README.md CHANGED
@@ -1,49 +1,321 @@
1
1
  # @nimpl/i18n
2
2
 
3
- (Former [next-translation](https://www.npmjs.com/package/next-translation))
3
+ <PackageLinks npmName="@nimpl/i18n" githubName="nimpl-i18n" />
4
4
 
5
- i18n library designed primarily with server components in mind and maximum optimization (due to the transfer of logic to the assembly stage and/or server side).
5
+ i18n library designed for React Server Components with maximum optimization (due to the transfer of logic to the assembly stage and/or server side).
6
6
 
7
- Visit https://nimpl.dev/docs/i18n to view the full documentation.
7
+ <!---robin-->
8
8
 
9
- ## Why one more library?
9
+ Read the documentation in a convenient interface at [nimpl.dev/docs/i18n](https://nimpl.dev/docs/i18n)
10
10
 
11
- Server components are a recent feature in React. Existing translation libraries are not yet well-optimized for them. If they support it, then only by disabling Next.js static optimization.
11
+ <!---/robin-->
12
12
 
13
- This library is an attempt to create a highly optimized solution exclusively using the current capabilities of React.js, Next.js and node.js.
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm i @nimpl/i18n
17
+ ```
14
18
 
15
- ## Features
19
+ ## Why This Library?
16
20
 
17
- Support for loading translations during page rendering (_instead of the application build step_), allowing for up-to-date translations with ISR or SSR;
21
+ Most i18n libraries either load the entire dictionary on the client or disable static optimization when working with Server Components.
18
22
 
19
- Support for revalidation logic, i.e. you can specify how often translations should be updated (_or that they should not be updated at all_);
23
+ This library resolves translations on the server. Client components only receive the specific precompiled keys they need through the `Transmitter` wrapper - not the whole dictionary. Pages remain statically generated with translations baked into the HTML.
20
24
 
21
- Optimized caching system - not a single extra request will be sent, even with parallel building (which is now enabled by default);
25
+ ## Quick Start
22
26
 
23
- Passing only those translations to the client that are needed on the client;
27
+ ### 1. Create i18n Configuration
24
28
 
25
- Embedding parameters in client translations on the server;
29
+ Create a configuration file (e.g., `src/i18n.ts`):
26
30
 
27
- Support for html entities.
31
+ ```ts
32
+ import { initialize } from "@nimpl/i18n/initialize";
33
+ import fs from "fs/promises";
28
34
 
29
- ## Installation
35
+ export const { getTranslation, ServerTranslation, Transmitter, revalidate } = initialize({
36
+ load: async (language) => {
37
+ const data = await fs.readFile(`./translations/${language}.json`, "utf-8");
38
+ return JSON.parse(data);
39
+ },
40
+ getLanguage: async () => {
41
+ // Use next/root-params, @nimpl/getters, @nimpl/context or implement your own language detection
42
+ const { params } = await import("@nimpl/getters/get-params").then(m => m.getParams());
43
+ return params.lang as string;
44
+ },
45
+ languages: ["en", "de", "fr"],
46
+ cache: true,
47
+ });
48
+ ```
30
49
 
31
- **Using npm:**
50
+ ### 2. Set Up Routes
32
51
 
33
- ```bash
34
- npm i @nimpl/i18n
52
+ ```
53
+ app/
54
+ [lang]/
55
+ layout.tsx
56
+ page.tsx
35
57
  ```
36
58
 
37
- **Using yarn:**
59
+ ```tsx
60
+ // app/[lang]/layout.tsx
61
+ export default function RootLayout({
62
+ children,
63
+ params,
64
+ }: {
65
+ children: React.ReactNode;
66
+ params: { lang: string };
67
+ }) {
68
+ return (
69
+ <html lang={params.lang}>
70
+ <body>{children}</body>
71
+ </html>
72
+ );
73
+ }
74
+ ```
38
75
 
39
- ```bash
40
- yarn add @nimpl/i18n
76
+ ### 3. Use Translations
77
+
78
+ ```tsx
79
+ // app/[lang]/page.tsx
80
+ import { getTranslation } from "@/i18n";
81
+
82
+ export default async function Page() {
83
+ const { t } = await getTranslation();
84
+
85
+ return <h1>{t("home.title")}</h1>;
86
+ }
87
+ ```
88
+
89
+ ## API
90
+
91
+ ### initialize
92
+
93
+ Creates configured i18n functions. Call once and export the returned functions.
94
+
95
+ ```ts
96
+ import { initialize } from "@nimpl/i18n/initialize";
97
+
98
+ export const { getTranslation, ServerTranslation, Transmitter, revalidate } = initialize(config);
99
+ ```
100
+
101
+ #### Config Options
102
+
103
+ | Option | Type | Required | Description |
104
+ |--------|------|----------|-------------|
105
+ | `load` | `(language: string) => Promise<object>` | Yes | Async function that returns translations for a language |
106
+ | `getLanguage` | `() => Promise<string>` | Yes | Function to determine current language |
107
+ | `languages` | `string[]` | Yes | Array of supported language codes |
108
+ | `cache` | `boolean` | No | Enable translation caching (recommended for production) |
109
+
110
+ ### getTranslation
111
+
112
+ Server-side function for translations in React Server Components.
113
+
114
+ ```tsx
115
+ import { getTranslation } from "@/i18n";
116
+
117
+ export default async function Page() {
118
+ const { t, language } = await getTranslation();
119
+
120
+ return <h1>{t("home.title")}</h1>;
121
+ }
122
+ ```
123
+
124
+ With explicit language (useful for `generateMetadata`):
125
+
126
+ ```tsx
127
+ export async function generateMetadata({ params }: { params: { lang: string } }) {
128
+ const { t } = await getTranslation({ language: params.lang });
129
+
130
+ return { title: t("home.meta.title") };
131
+ }
132
+ ```
133
+
134
+ With namespace:
135
+
136
+ ```tsx
137
+ const { t } = await getTranslation({ namespace: "home" });
138
+ t("title"); // Equivalent to t("home.title")
139
+ ```
140
+
141
+ ### Transmitter
142
+
143
+ Server component that passes translations to client components. Wrap client components that need translations.
144
+
145
+ ```tsx
146
+ import { Transmitter } from "@/i18n";
147
+ import Counter from "./Counter";
148
+
149
+ export default async function Page() {
150
+ return (
151
+ <Transmitter terms={["counter", "shared.buttons"]}>
152
+ <Counter />
153
+ </Transmitter>
154
+ );
155
+ }
156
+ ```
157
+
158
+ With explicit language:
159
+
160
+ ```tsx
161
+ <Transmitter terms={["counter"]} language="fr">
162
+ <Counter />
163
+ </Transmitter>
164
+ ```
165
+
166
+ ### ServerTranslation
167
+
168
+ Server component for complex translations with embedded JSX.
169
+
170
+ ```tsx
171
+ import { ServerTranslation } from "@/i18n";
172
+
173
+ // Translation: "Read our <link>documentation</link> for more info"
174
+ export default async function Page() {
175
+ return (
176
+ <ServerTranslation
177
+ term="home.description"
178
+ components={{ link: <a href="/docs" /> }}
179
+ />
180
+ );
181
+ }
182
+ ```
183
+
184
+ With variables:
185
+
186
+ ```tsx
187
+ // Translation: "We have <b>{{count}}</b> products"
188
+ <ServerTranslation
189
+ term="products.count"
190
+ components={{ b: <strong /> }}
191
+ query={{ count: 42 }}
192
+ />
193
+ ```
194
+
195
+ ### useTranslation
196
+
197
+ Client-side hook. Requires `Transmitter` in a parent Server Component.
198
+
199
+ ```tsx
200
+ "use client";
201
+
202
+ import { useTranslation } from "@nimpl/i18n/useTranslation";
203
+
204
+ export default function Counter() {
205
+ const { t } = useTranslation();
206
+
207
+ return <button>{t("counter.increment")}</button>;
208
+ }
209
+ ```
210
+
211
+ With namespace:
212
+
213
+ ```tsx
214
+ const { t } = useTranslation({ namespace: "counter" });
215
+ ```
216
+
217
+ ### ClientTranslation
218
+
219
+ Client component for complex translations with embedded JSX.
220
+
221
+ ```tsx
222
+ "use client";
223
+
224
+ import { ClientTranslation } from "@nimpl/i18n/ClientTranslation";
225
+
226
+ // Translation: "Read our <link>documentation</link>"
227
+ export default function Info() {
228
+ return (
229
+ <ClientTranslation
230
+ term="info.description"
231
+ components={{ link: <a href="/docs" /> }}
232
+ />
233
+ );
234
+ }
235
+ ```
236
+
237
+ ### revalidate
238
+
239
+ Refresh cached translations for a language.
240
+
241
+ ```tsx
242
+ import { revalidate } from "@/i18n";
243
+
244
+ // Foreground revalidation (blocks until complete)
245
+ await revalidate("en");
246
+
247
+ // Background revalidation (non-blocking)
248
+ await revalidate("en", true);
249
+ ```
250
+
251
+ ## Variable Interpolation
252
+
253
+ Use `{{variable}}` syntax in translations:
254
+
255
+ ```json
256
+ {
257
+ "greeting": "Hello, {{name}}!",
258
+ "items": "You have {{count}} items"
259
+ }
260
+ ```
261
+
262
+ ```tsx
263
+ t("greeting", { query: { name: "Alex" } });
264
+ // → "Hello, Alex!"
265
+
266
+ t("items", { query: { count: 5 } });
267
+ // → "You have 5 items"
268
+ ```
269
+
270
+ Use `removeUnusedQueries` to strip undefined variables:
271
+
272
+ ```tsx
273
+ // Translation: "Hello, {{name}}! Role: {{role}}"
274
+ t("welcome", { query: { name: "Alex" }, removeUnusedQueries: true });
275
+ // → "Hello, Alex! Role: "
41
276
  ```
42
277
 
43
- ## Additionally
278
+ ### Server-Side Query Injection
279
+
280
+ Pass dynamic values to client translations from the server to improve client performance:
281
+
282
+ ```tsx
283
+ <Transmitter
284
+ terms={[
285
+ "pricing",
286
+ ["welcome", { query: { stage: process.env.NODE_ENV } }],
287
+ ]}
288
+ >
289
+ <ClientComponent />
290
+ </Transmitter>
291
+ ```
292
+
293
+ ## Namespaces
294
+
295
+ Access nested keys with dot notation or colon prefix:
296
+
297
+ ```tsx
298
+ // Dot notation
299
+ t("header.nav.home");
300
+
301
+ // Namespace in options
302
+ const { t } = await getTranslation({ namespace: "header" });
303
+ t("nav.home");
304
+
305
+ // Colon prefix (overrides default namespace)
306
+ t("footer:copyright");
307
+ ```
308
+
309
+ ## Notes
310
+
311
+ - Client components (`useTranslation`, `ClientTranslation`) inherit language from server parents
312
+ - The `terms` array accepts namespace prefixes (e.g., `"nav"`) or specific keys (e.g., `"nav.home"`)
313
+ - Use `next/root-params`, `@nimpl/getters` or `@nimpl/context` for automatic route parameter detection
314
+
315
+ ## Examples
44
316
 
45
- Create tasks with wishes, ideas, difficulties, etc. All of them will definitely be considered and thought over.
317
+ - [Base example](https://github.com/alexdln/nimpl-i18n/tree/main/examples/base)
46
318
 
47
319
  ## License
48
320
 
49
- [MIT](https://github.com/alexdln/nimpl-i18n/blob/main/LICENSE)
321
+ MIT
@@ -4,12 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ClientTranslation = void 0;
7
8
  const react_1 = __importDefault(require("react"));
8
- const Translation_1 = __importDefault(require("./lib/Translation"));
9
- const useTranslation_1 = __importDefault(require("./useTranslation"));
10
- const ClientTranslation = ({ term, components, query, removeUnusedQueries }) => {
11
- const { t } = (0, useTranslation_1.default)();
9
+ const Translation_1 = require("./lib/Translation");
10
+ const useTranslation_1 = require("./useTranslation");
11
+ const ClientTranslation = ({ term, components, query, removeUnusedQueries, }) => {
12
+ const { t } = (0, useTranslation_1.useTranslation)();
12
13
  const text = t(term, { query, removeUnusedQueries });
13
- return react_1.default.createElement(Translation_1.default, { term: term, text: text, components: components });
14
+ return react_1.default.createElement(Translation_1.Translation, { term: term, text: text, components: components });
14
15
  };
15
- exports.default = ClientTranslation;
16
+ exports.ClientTranslation = ClientTranslation;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initialize = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const get_translation_core_1 = require("./lib/get-translation-core");
9
+ const translation_core_1 = require("./lib/translation-core");
10
+ const transmitter_core_1 = require("./lib/transmitter-core");
11
+ const initialize = (config) => {
12
+ const cache = new Map();
13
+ const loadTranslates = async (language, revalidate) => {
14
+ const item = cache.get(language);
15
+ if (!revalidate && item)
16
+ return item;
17
+ if (item && "then" in item) {
18
+ return item;
19
+ }
20
+ const newData = await config.load(language);
21
+ cache.set(language, newData);
22
+ return newData;
23
+ };
24
+ const getTranslation = async (options) => {
25
+ const { language, ...rest } = options || {};
26
+ const targetLanguage = language || (await config.getLanguage());
27
+ let dictionary;
28
+ if (config.cache) {
29
+ dictionary = await loadTranslates(targetLanguage);
30
+ }
31
+ else {
32
+ dictionary = await revalidate(targetLanguage);
33
+ }
34
+ if (!language) {
35
+ throw new Error("Unable to get the language in getTranslation. Please check the getLanguage method in the configuration file or pass the language as an argument.");
36
+ }
37
+ return (0, get_translation_core_1.getTranslationCore)({ ...rest, language: targetLanguage, dictionary });
38
+ };
39
+ const ServerTranslation = async (options) => {
40
+ const { language, ...rest } = options || {};
41
+ const targetLanguage = language || (await config.getLanguage());
42
+ let dictionary;
43
+ if (config.cache) {
44
+ dictionary = await loadTranslates(targetLanguage);
45
+ }
46
+ else {
47
+ dictionary = await revalidate(targetLanguage);
48
+ }
49
+ if (!language) {
50
+ throw new Error("Unable to get the language in ServerTranslation. Please check the getLanguage method in the configuration file or pass the language as an argument.");
51
+ }
52
+ return react_1.default.createElement(translation_core_1.TranslationCore, { ...rest, language: targetLanguage, dictionary: dictionary });
53
+ };
54
+ const Transmitter = async (options) => {
55
+ const { language, ...rest } = options || {};
56
+ const targetLanguage = language || (await config.getLanguage());
57
+ let dictionary;
58
+ if (config.cache) {
59
+ dictionary = await loadTranslates(targetLanguage);
60
+ }
61
+ else {
62
+ dictionary = await revalidate(targetLanguage);
63
+ }
64
+ if (!language) {
65
+ throw new Error("Unable to get the language in Transmitter. Please check the getLanguage method in the configuration file or pass the language as an argument.");
66
+ }
67
+ return react_1.default.createElement(transmitter_core_1.TransmitterCore, { ...rest, language: targetLanguage, dictionary: dictionary });
68
+ };
69
+ const revalidate = async (language, background = false) => {
70
+ const item = cache.get(language);
71
+ if (item && "then" in item)
72
+ return item;
73
+ if (background) {
74
+ const newData = await loadTranslates(language, true);
75
+ return newData;
76
+ }
77
+ const newDataPromise = loadTranslates(language, true);
78
+ cache.set(language, newDataPromise);
79
+ return newDataPromise;
80
+ };
81
+ return {
82
+ Transmitter,
83
+ ServerTranslation,
84
+ getTranslation,
85
+ revalidate,
86
+ };
87
+ };
88
+ exports.initialize = initialize;
@@ -34,13 +34,14 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.ClientI18nProvider = void 0;
37
38
  const react_1 = __importStar(require("react"));
38
39
  const ClientI18nContext_1 = require("./ClientI18nContext");
39
- const ClientI18nProvider = ({ translates, children, language, cleanThread }) => {
40
+ const ClientI18nProvider = ({ translates, children, language, cleanThread, }) => {
40
41
  const prevTranslates = (0, react_1.useContext)(ClientI18nContext_1.ClientI18nContext);
41
42
  if (cleanThread) {
42
43
  Object.assign(translates, prevTranslates?.translates);
43
44
  }
44
45
  return react_1.default.createElement(ClientI18nContext_1.ClientI18nContext.Provider, { value: { language, translates } }, children);
45
46
  };
46
- exports.default = ClientI18nProvider;
47
+ exports.ClientI18nProvider = ClientI18nProvider;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Translation = void 0;
7
8
  const react_1 = __importDefault(require("react"));
8
9
  const parseTag = (tag) => {
9
10
  const isSelfClosing = tag.endsWith("/>");
@@ -89,4 +90,4 @@ const Translation = ({ term, text, components }) => {
89
90
  }
90
91
  return parts;
91
92
  };
92
- exports.default = Translation;
93
+ exports.Translation = Translation;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTranslate = void 0;
4
+ const html_entities_1 = require("html-entities");
5
+ const injectQuery_1 = require("./injectQuery");
6
+ const formatTranslate = ({ term, text, removeUnusedQueries, query, parseEntities }) => {
7
+ let newTranslate = text;
8
+ if (query) {
9
+ newTranslate = (0, injectQuery_1.injectQuery)({ term, text: newTranslate, query, removeUnusedQueries });
10
+ }
11
+ if (parseEntities === undefined || parseEntities === true) {
12
+ newTranslate = (0, html_entities_1.decode)(newTranslate, { scope: "strict" });
13
+ }
14
+ return newTranslate;
15
+ };
16
+ exports.formatTranslate = formatTranslate;
@@ -3,16 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getTranslationCore = void 0;
6
7
  const object_path_1 = __importDefault(require("object-path"));
7
- const formatServerTranslate_1 = __importDefault(require("./lib/formatServerTranslate"));
8
- const loadI18nData_1 = __importDefault(require("./lib/loadI18nData"));
9
- const getTranslation = async (options) => {
10
- const { language: argLanguage, namespace } = options || {};
11
- const { dictionary, language: configLanguage } = await (0, loadI18nData_1.default)();
12
- const language = argLanguage || configLanguage;
13
- if (!language) {
14
- throw new Error("Unable to get the language in the createTranslation function. Please check the getLanguage method in the configuration file or pass the language as an argument.");
15
- }
8
+ const format_translate_1 = require("./format-translate");
9
+ const getTranslationCore = (options) => {
10
+ const { language, namespace, dictionary } = options || {};
16
11
  const namespaceDictionary = namespace ? object_path_1.default.get(dictionary, namespace) : dictionary;
17
12
  const t = (term, opts) => {
18
13
  let termDictionary = namespaceDictionary;
@@ -26,8 +21,8 @@ const getTranslation = async (options) => {
26
21
  const fullTerm = `${termNamespace ? `${termNamespace}.` : ""}${termKey}`;
27
22
  if (typeof translation !== "string" || !translation)
28
23
  return fullTerm;
29
- return (0, formatServerTranslate_1.default)({ term: fullTerm, text: translation, parseEntities: true, ...opts });
24
+ return (0, format_translate_1.formatTranslate)({ term: fullTerm, text: translation, parseEntities: true, ...opts });
30
25
  };
31
26
  return { t, language };
32
27
  };
33
- exports.default = getTranslation;
28
+ exports.getTranslationCore = getTranslationCore;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.injectQuery = void 0;
3
4
  const injectQuery = ({ text, query, removeUnusedQueries }) => {
4
5
  let result = "";
5
6
  let i = 0;
@@ -27,4 +28,4 @@ const injectQuery = ({ text, query, removeUnusedQueries }) => {
27
28
  }
28
29
  return result;
29
30
  };
30
- exports.default = injectQuery;
31
+ exports.injectQuery = injectQuery;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseEntities = void 0;
3
4
  const html_entities_1 = require("html-entities");
4
5
  const parseEntities = (translate) => {
5
6
  return (0, html_entities_1.decode)(translate, { scope: "strict" });
6
7
  };
7
- exports.default = parseEntities;
8
+ exports.parseEntities = parseEntities;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TranslationCore = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const Translation_1 = require("./Translation");
9
+ const get_translation_core_1 = require("./get-translation-core");
10
+ const TranslationCore = ({ dictionary, namespace, term, components, query, removeUnusedQueries, language, }) => {
11
+ const { t } = (0, get_translation_core_1.getTranslationCore)({ language, namespace, dictionary });
12
+ const text = t(term, { query, removeUnusedQueries });
13
+ return react_1.default.createElement(Translation_1.Translation, { term: term, text: text, components: components });
14
+ };
15
+ exports.TranslationCore = TranslationCore;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TransmitterCore = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const object_path_1 = __importDefault(require("object-path"));
9
+ const ClientI18nProvider_1 = require("./ClientI18nProvider");
10
+ const format_translate_1 = require("./format-translate");
11
+ const formatTranslates = (result, targetKey, translates, opts = {}) => {
12
+ if (!translates)
13
+ return;
14
+ if (typeof translates === "string") {
15
+ result[targetKey] = (0, format_translate_1.formatTranslate)({ term: targetKey, text: translates, parseEntities: true, ...opts });
16
+ }
17
+ else {
18
+ Object.entries(translates).forEach(([subKey, translate]) => {
19
+ formatTranslates(result, `${targetKey}.${subKey}`, translate, opts);
20
+ });
21
+ }
22
+ };
23
+ const TransmitterCore = async ({ dictionary, language, terms, children, cleanThread, }) => {
24
+ const result = {};
25
+ terms.forEach((term) => {
26
+ if (Array.isArray(term)) {
27
+ const [termKey, opts] = term;
28
+ const translates = object_path_1.default.get(dictionary, termKey);
29
+ formatTranslates(result, termKey, translates, opts);
30
+ }
31
+ else {
32
+ const translates = object_path_1.default.get(dictionary, term);
33
+ formatTranslates(result, term, translates);
34
+ }
35
+ });
36
+ return (react_1.default.createElement(ClientI18nProvider_1.ClientI18nProvider, { language: language, translates: result, cleanThread: cleanThread }, children));
37
+ };
38
+ exports.TransmitterCore = TransmitterCore;
@@ -1,11 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTranslation = void 0;
6
4
  const react_1 = require("react");
7
5
  const ClientI18nContext_1 = require("./lib/ClientI18nContext");
8
- const injectQuery_1 = __importDefault(require("./lib/injectQuery"));
6
+ const injectQuery_1 = require("./lib/injectQuery");
9
7
  const useTranslation = ({ namespace } = {}) => {
10
8
  const context = (0, react_1.useContext)(ClientI18nContext_1.ClientI18nContext);
11
9
  if (!context) {
@@ -24,7 +22,7 @@ const useTranslation = ({ namespace } = {}) => {
24
22
  if (!translation)
25
23
  return termKey;
26
24
  if (opts?.query) {
27
- return (0, injectQuery_1.default)({
25
+ return (0, injectQuery_1.injectQuery)({
28
26
  term,
29
27
  text: translation,
30
28
  query: opts.query,
@@ -35,4 +33,4 @@ const useTranslation = ({ namespace } = {}) => {
35
33
  };
36
34
  return { t, language };
37
35
  };
38
- exports.default = useTranslation;
36
+ exports.useTranslation = useTranslation;
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { type Config, type Translates } from "./types";
3
+ import { type GetTranslationCoreOptions } from "./lib/get-translation-core";
4
+ import { type TranslationCoreProps } from "./lib/translation-core";
5
+ import { type TransmitterCoreProps } from "./lib/transmitter-core";
6
+ export declare const initialize: (config: Config) => {
7
+ Transmitter: (options: Omit<TransmitterCoreProps, "dictionary" | "language"> & {
8
+ language?: string;
9
+ }) => Promise<React.JSX.Element>;
10
+ ServerTranslation: (options: Omit<TranslationCoreProps, "dictionary" | "language"> & {
11
+ language?: string;
12
+ }) => Promise<React.JSX.Element>;
13
+ getTranslation: (options: Omit<GetTranslationCoreOptions, "dictionary" | "language"> & {
14
+ language?: string;
15
+ }) => Promise<import("./lib/get-translation-core").GetTranslationCoreReturnType>;
16
+ revalidate: (language: string, background?: boolean) => Promise<Translates>;
17
+ };
package/package.json CHANGED
@@ -1,31 +1,19 @@
1
1
  {
2
2
  "name": "@nimpl/i18n",
3
- "version": "0.0.0-experimental-47e75fe",
3
+ "version": "0.0.0-experimental-504b302",
4
4
  "description": "i18n library for working with translations in server and client components",
5
5
  "exports": {
6
6
  "./ClientTranslation": {
7
7
  "default": "./dist/ClientTranslation.js"
8
8
  },
9
- "./getTranslation": {
10
- "default": "./dist/getTranslation.js"
11
- },
12
- "./i18n": {
13
- "default": "./dist/i18n.js"
14
- },
15
- "./I18nTransmitter": {
16
- "default": "./dist/I18nTransmitter.js"
17
- },
18
- "./ServerTranslation": {
19
- "default": "./dist/ServerTranslation.js"
9
+ "./initialize": {
10
+ "default": "./dist/initialize.js"
20
11
  },
21
12
  "./types": {
22
13
  "default": "./dist/types.js"
23
14
  },
24
15
  "./useTranslation": {
25
16
  "default": "./dist/useTranslation.js"
26
- },
27
- "./clientConfig": {
28
- "default": "./dist/clientConfig.js"
29
17
  }
30
18
  },
31
19
  "files": [
@@ -68,7 +56,6 @@
68
56
  "react": ">= 19.1.0"
69
57
  },
70
58
  "dependencies": {
71
- "@nimpl/getters": "2.2.2",
72
59
  "html-entities": "2.6.0",
73
60
  "object-path": "0.11.8"
74
61
  }
package/types.d.ts CHANGED
@@ -20,3 +20,13 @@ export type I18nContextType = {
20
20
  [key: string]: string;
21
21
  };
22
22
  } | null;
23
+ export type Meta = {
24
+ lastUpdated: number;
25
+ isRevalidated?: boolean;
26
+ } & Record<string, unknown>;
27
+ export type Config = {
28
+ load(key: string, meta?: Meta): Promise<Translates>;
29
+ getLanguage(): Promise<string>;
30
+ languages: string[];
31
+ cache?: boolean;
32
+ };
@@ -3,7 +3,7 @@ type GetTranslationReturnType = {
3
3
  t: (term: string, opts?: I18nOptions) => string;
4
4
  language: string;
5
5
  };
6
- declare const useTranslation: ({ namespace }?: {
6
+ export declare const useTranslation: ({ namespace }?: {
7
7
  namespace?: string;
8
8
  }) => GetTranslationReturnType;
9
- export default useTranslation;
9
+ export {};
@@ -1,10 +0,0 @@
1
- import React from "react";
2
- import { type I18nOptions } from "./types";
3
- export type I18nTransmitterProps = {
4
- terms: (string | [string, I18nOptions])[];
5
- children: React.ReactNode;
6
- cleanThread?: boolean;
7
- language?: string;
8
- };
9
- declare const I18nTransmitter: React.FC<I18nTransmitterProps>;
10
- export default I18nTransmitter;
@@ -1,12 +0,0 @@
1
- import React from "react";
2
- import { type I18nOptions } from "./types";
3
- import { type TranslationProps } from "./lib/Translation";
4
- type ServerTranslationProps = {
5
- term: string;
6
- components?: TranslationProps["components"];
7
- query?: I18nOptions["query"];
8
- removeUnusedQueries?: I18nOptions["removeUnusedQueries"];
9
- language?: string;
10
- };
11
- declare const ServerTranslation: React.FC<ServerTranslationProps>;
12
- export default ServerTranslation;
package/clientConfig.d.ts DELETED
File without changes
@@ -1,43 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
7
- const object_path_1 = __importDefault(require("object-path"));
8
- const ClientI18nProvider_1 = __importDefault(require("./lib/ClientI18nProvider"));
9
- const formatServerTranslate_1 = __importDefault(require("./lib/formatServerTranslate"));
10
- const loadI18nData_1 = __importDefault(require("./lib/loadI18nData"));
11
- const formatServerTranslates = (result, targetKey, translates, opts = {}) => {
12
- if (!translates)
13
- return;
14
- if (typeof translates === "string") {
15
- result[targetKey] = (0, formatServerTranslate_1.default)({ term: targetKey, text: translates, parseEntities: true, ...opts });
16
- }
17
- else {
18
- Object.entries(translates).forEach(([subKey, translate]) => {
19
- formatServerTranslates(result, `${targetKey}.${subKey}`, translate, opts);
20
- });
21
- }
22
- };
23
- const I18nTransmitter = async ({ language: argLanguage, terms, children, cleanThread, }) => {
24
- const { dictionary, language: configLanguage } = await (0, loadI18nData_1.default)();
25
- const language = argLanguage || configLanguage;
26
- if (!language) {
27
- throw new Error("Unable to get the language in the createTranslation function. Please check the getLanguage method in the configuration file or pass the language as an argument.");
28
- }
29
- const result = {};
30
- terms.forEach((term) => {
31
- if (Array.isArray(term)) {
32
- const [termKey, opts] = term;
33
- const translates = object_path_1.default.get(dictionary, termKey);
34
- formatServerTranslates(result, termKey, translates, opts);
35
- }
36
- else {
37
- const translates = object_path_1.default.get(dictionary, term);
38
- formatServerTranslates(result, term, translates);
39
- }
40
- });
41
- return (react_1.default.createElement(ClientI18nProvider_1.default, { language: language, translates: result, cleanThread: cleanThread }, children));
42
- };
43
- exports.default = I18nTransmitter;
@@ -1,14 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
7
- const Translation_1 = __importDefault(require("./lib/Translation"));
8
- const getTranslation_1 = __importDefault(require("./getTranslation"));
9
- const ServerTranslation = async ({ term, components, query, removeUnusedQueries, language, }) => {
10
- const { t } = await (0, getTranslation_1.default)({ language });
11
- const text = t(term, { query, removeUnusedQueries });
12
- return react_1.default.createElement(Translation_1.default, { term: term, text: text, components: components });
13
- };
14
- exports.default = ServerTranslation;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- throw new Error("Please add the i18n wrapper to your next.config.js");
@@ -1,68 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- const fs_1 = __importDefault(require("fs"));
40
- const path_1 = __importDefault(require("path"));
41
- const url_1 = require("url");
42
- const CONFIG_PATH = path_1.default.join(process.cwd(), "nimpl-i18n.js");
43
- // Crutch bypass of conversion by the assembler to require
44
- const dynamicImport = new Function("p", "return import(p)");
45
- const getConfig = async () => {
46
- let clientConfig;
47
- if (fs_1.default.existsSync(CONFIG_PATH)) {
48
- const config = await dynamicImport((0, url_1.pathToFileURL)(CONFIG_PATH).href);
49
- clientConfig = config.default;
50
- }
51
- else {
52
- // @ts-expect-error will be imported from the alias configuration in webpack
53
- const config = await Promise.resolve().then(() => __importStar(require("@nimpl/i18n/clientConfig")));
54
- clientConfig = config.default;
55
- }
56
- const { load, getLanguage, languages } = clientConfig;
57
- if (!load) {
58
- throw new Error(`Can't find "load" method in configuration file - https://github.com/alexdln/nimpl-i18n#configuration`);
59
- }
60
- if (!languages) {
61
- throw new Error(`Can't find "languages" list in configuration file - https://github.com/alexdln/nimpl-i18n#configuration`);
62
- }
63
- if (!getLanguage) {
64
- throw new Error(`Can't find "getLanguage" method in configuration file - https://github.com/alexdln/nimpl-i18n#configuration`);
65
- }
66
- return clientConfig;
67
- };
68
- exports.default = getConfig;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,10 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isPromise = void 0;
4
- const isPromise = (el) => {
5
- if (el && typeof el === "object" && "then" in el) {
6
- return true;
7
- }
8
- return false;
9
- };
10
- exports.isPromise = isPromise;
package/dist/i18n.js DELETED
@@ -1,29 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const fs_1 = require("fs");
7
- const path_1 = __importDefault(require("path"));
8
- const CONFIG_PATH = path_1.default.join(process.cwd(), "nimpl-i18n.js");
9
- const i18n = () => {
10
- return (nextConfig = {}) => {
11
- return {
12
- ...nextConfig,
13
- webpack: (config, options) => {
14
- if (!(0, fs_1.existsSync)(CONFIG_PATH))
15
- throw new Error("Please create and configure nimpl-i18n.js");
16
- config.resolve ||= {};
17
- config.resolve.alias = {
18
- ...config.resolve.alias,
19
- "@nimpl/i18n/clientConfig": CONFIG_PATH,
20
- };
21
- if (typeof nextConfig.webpack === "function") {
22
- return nextConfig.webpack(config, options);
23
- }
24
- return config;
25
- },
26
- };
27
- };
28
- };
29
- exports.default = i18n;
@@ -1,18 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const html_entities_1 = require("html-entities");
7
- const injectQuery_1 = __importDefault(require("./injectQuery"));
8
- const formatServerTranslate = ({ term, text, removeUnusedQueries, query, parseEntities }) => {
9
- let newTranslate = text;
10
- if (query) {
11
- newTranslate = (0, injectQuery_1.default)({ term, text: newTranslate, query, removeUnusedQueries });
12
- }
13
- if (parseEntities === undefined || parseEntities === true) {
14
- newTranslate = (0, html_entities_1.decode)(newTranslate, { scope: "strict" });
15
- }
16
- return newTranslate;
17
- };
18
- exports.default = formatServerTranslate;
@@ -1,25 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const getConfig_1 = __importDefault(require("../configuration/getConfig"));
7
- const get_pathname_1 = require("@nimpl/getters/get-pathname");
8
- const get_params_1 = require("@nimpl/getters/get-params");
9
- const loadI18nData = async () => {
10
- const config = await (0, getConfig_1.default)();
11
- const language = await config.getLanguage({
12
- get pathname() {
13
- return (0, get_pathname_1.getPathname)();
14
- },
15
- get params() {
16
- return (0, get_params_1.getParams)();
17
- },
18
- });
19
- if (!language || !config.languages.includes(language)) {
20
- throw new Error(`Can\' load data for language "${language}", valid languages are: ${config.languages.join(", ")}`);
21
- }
22
- const dictionary = await config.load(language);
23
- return { dictionary, language };
24
- };
25
- exports.default = loadI18nData;
@@ -1,10 +0,0 @@
1
- import { type I18nOptions } from "./types";
2
- type GetTranslationReturnType = {
3
- t: (term: string, opts?: I18nOptions) => string;
4
- language: string;
5
- };
6
- declare const getTranslation: (options?: {
7
- language?: string;
8
- namespace?: string;
9
- }) => Promise<GetTranslationReturnType>;
10
- export default getTranslation;
package/i18n.d.ts DELETED
@@ -1,2 +0,0 @@
1
- declare const i18n: () => (nextConfig?: any) => any;
2
- export default i18n;