@djangocfg/i18n 2.1.190 → 2.1.192

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
@@ -181,16 +181,86 @@ function MyComponent() {
181
181
  }
182
182
  ```
183
183
 
184
+ ## Type-safe next-intl Integration
185
+
186
+ Override `useTranslations` in your app's `global.d.ts` to get compile-time key validation.
187
+
188
+ ### 1. Merge translations (flat, no namespace)
189
+
190
+ ```ts
191
+ // i18n/request.ts
192
+ import { en as baseEn } from '@djangocfg/i18n/locales';
193
+ import { en as appEn } from './locales';
194
+
195
+ const locales = {
196
+ en: { ...baseEn, ...appEn },
197
+ };
198
+ ```
199
+
200
+ ### 2. Add `global.d.ts` to your app root
201
+
202
+ ```ts
203
+ // global.d.ts
204
+ type _Messages = import('@djangocfg/i18n').I18nTranslations &
205
+ import('./i18n/locales/types').AppTranslations;
206
+
207
+ type _NSKeys = import('@djangocfg/i18n').NamespaceKeys<
208
+ _Messages,
209
+ import('@djangocfg/i18n').NestedKeyOf<_Messages>
210
+ >;
211
+
212
+ declare module 'next-intl' {
213
+ export function useTranslations<NS extends _NSKeys>(
214
+ namespace: NS,
215
+ ): import('@djangocfg/i18n').IntlTranslator<_Messages, NS>;
216
+ }
217
+ ```
218
+
219
+ ### 3. Include in tsconfig.json
220
+
221
+ ```json
222
+ { "include": ["global.d.ts", "app/**/*.ts", ...] }
223
+ ```
224
+
225
+ Now invalid keys and namespaces produce compile errors:
226
+
227
+ ```ts
228
+ const t = useTranslations('machines');
229
+ t('title'); // OK
230
+ t('dialogs.delete.title'); // OK
231
+ t('NONEXISTENT'); // Error!
232
+
233
+ useTranslations('BOGUS'); // Error!
234
+ ```
235
+
236
+ > **Why not `use-intl` AppConfig?** The standard `declare module 'use-intl' { interface AppConfig }` augmentation doesn't propagate through pnpm's nested `node_modules`. Overriding `useTranslations` in `next-intl` directly works reliably.
237
+
238
+ ### Exported utility types
239
+
240
+ | Type | Description |
241
+ |------|-------------|
242
+ | `NestedKeyOf<T>` | All dot-separated paths (leaves + namespaces) |
243
+ | `NestedValueOf<T, P>` | Resolve value type by dot path |
244
+ | `NamespaceKeys<T, A>` | Paths resolving to objects (valid namespaces) |
245
+ | `MessageKeys<T, A>` | Paths resolving to strings (valid keys) |
246
+ | `IntlTranslator<M, NS>` | Type-safe translator with `t()`, `rich()`, `has()`, `raw()` |
247
+
184
248
  ## Extending Translations
185
249
 
186
- ### mergeTranslations()
250
+ ### Spread merge (recommended for next-intl apps)
251
+
252
+ ```ts
253
+ import { en as baseEn } from '@djangocfg/i18n/locales';
254
+ const messages = { ...baseEn, ...appEn };
255
+ ```
256
+
257
+ ### mergeTranslations() (deep merge with overrides)
187
258
 
188
259
  ```tsx
189
260
  import { mergeTranslations, ru } from '@djangocfg/i18n'
190
261
 
191
262
  const customRu = mergeTranslations(ru, {
192
263
  ui: { select: { placeholder: 'Выберите...' } },
193
- app: { title: 'Мое приложение' }
194
264
  })
195
265
  ```
196
266
 
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as react from 'react';
1
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
3
 
3
4
  /**
@@ -72,6 +73,27 @@ type TypedTranslationFn<T extends object> = TranslationFn<T>;
72
73
  * ```
73
74
  */
74
75
  type TranslationKeys<T extends object> = PathKeys<T>;
76
+ /** All dot-separated paths (both leaves and namespaces) */
77
+ type NestedKeyOf<T> = T extends object ? {
78
+ [K in keyof T & string]: K | `${K}.${NestedKeyOf<T[K]>}`;
79
+ }[keyof T & string] : never;
80
+ /** Resolve nested value by dot-separated path */
81
+ type NestedValueOf<T, P extends string> = P extends `${infer K}.${infer R}` ? K extends keyof T ? NestedValueOf<T[K], R> : never : P extends keyof T ? T[P] : never;
82
+ /** Paths that resolve to objects (valid useTranslations namespaces) */
83
+ type NamespaceKeys<T, A extends string> = {
84
+ [K in A]: NestedValueOf<T, K> extends string ? never : K;
85
+ }[A];
86
+ /** Paths that resolve to strings (valid t() keys) */
87
+ type MessageKeys<T, A extends string> = {
88
+ [K in A]: NestedValueOf<T, K> extends string ? K : never;
89
+ }[A];
90
+ /** Type-safe translator returned by useTranslations(namespace) */
91
+ type IntlTranslator<Messages, NS extends NamespaceKeys<Messages, NestedKeyOf<Messages>>> = {
92
+ <K extends MessageKeys<NestedValueOf<Messages, NS>, NestedKeyOf<NestedValueOf<Messages, NS>>>>(key: K, values?: Record<string, unknown>): string;
93
+ rich<K extends MessageKeys<NestedValueOf<Messages, NS>, NestedKeyOf<NestedValueOf<Messages, NS>>>>(key: K, values?: Record<string, unknown>): react.ReactNode;
94
+ has(key: NestedKeyOf<NestedValueOf<Messages, NS>>): boolean;
95
+ raw(key: NestedKeyOf<NestedValueOf<Messages, NS>>): unknown;
96
+ };
75
97
 
76
98
  /**
77
99
  * All valid translation keys for base I18nTranslations
@@ -1019,4 +1041,4 @@ declare function mergeTranslations<T extends Record<string, unknown> = Record<st
1019
1041
  */
1020
1042
  declare function createTranslations(base: I18nTranslations, ...overrides: PartialTranslations[]): I18nTranslations;
1021
1043
 
1022
- export { type DeepPartial, type I18nContextValue, type I18nKeys, I18nProvider, type I18nProviderProps, type I18nTranslationFn, type I18nTranslations, type LocaleCode, type PartialTranslations, type PathKeys, type TranslationFn, type TranslationKeys, type TypedTranslationFn, createTranslations, en, getT, interpolate, ko, mergeTranslations, ru, useI18n, useLocale, useT, useTranslation, useTypedT };
1044
+ export { type DeepPartial, type I18nContextValue, type I18nKeys, I18nProvider, type I18nProviderProps, type I18nTranslationFn, type I18nTranslations, type IntlTranslator, type LocaleCode, type MessageKeys, type NamespaceKeys, type NestedKeyOf, type NestedValueOf, type PartialTranslations, type PathKeys, type TranslationFn, type TranslationKeys, type TypedTranslationFn, createTranslations, en, getT, interpolate, ko, mergeTranslations, ru, useI18n, useLocale, useT, useTranslation, useTypedT };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/i18n",
3
- "version": "2.1.190",
3
+ "version": "2.1.192",
4
4
  "description": "Lightweight i18n library for @djangocfg packages with built-in translations for English, Russian, and Korean",
5
5
  "keywords": [
6
6
  "i18n",
@@ -72,13 +72,13 @@
72
72
  "react": "^18.0.0 || ^19.0.0"
73
73
  },
74
74
  "dependencies": {
75
- "@djangocfg/llm": "^2.1.190",
75
+ "@djangocfg/llm": "^2.1.192",
76
76
  "citty": "^0.1.6",
77
77
  "consola": "^3.4.0",
78
78
  "jiti": "^2.4.2"
79
79
  },
80
80
  "devDependencies": {
81
- "@djangocfg/typescript-config": "^2.1.190",
81
+ "@djangocfg/typescript-config": "^2.1.192",
82
82
  "@types/node": "^25.2.3",
83
83
  "@types/react": "^19.1.0",
84
84
  "eslint": "^9.37.0",
package/src/index.ts CHANGED
@@ -19,6 +19,11 @@ export type {
19
19
  TranslationFn,
20
20
  TypedTranslationFn,
21
21
  TranslationKeys,
22
+ NestedKeyOf,
23
+ NestedValueOf,
24
+ NamespaceKeys,
25
+ MessageKeys,
26
+ IntlTranslator,
22
27
  } from './utils/path-keys'
23
28
 
24
29
  // Locales
@@ -1,4 +1,4 @@
1
1
  export { interpolate } from './interpolate'
2
2
  export { mergeTranslations, createTranslations } from './merge'
3
3
  export { getNestedValue } from './get-value'
4
- export type { PathKeys, TranslationFn, TypedTranslationFn, TranslationKeys } from './path-keys'
4
+ export type { PathKeys, TranslationFn, TypedTranslationFn, TranslationKeys, NestedKeyOf, NestedValueOf, NamespaceKeys, MessageKeys, IntlTranslator } from './path-keys'
@@ -83,3 +83,42 @@ export type TypedTranslationFn<T extends object> = TranslationFn<T>
83
83
  * ```
84
84
  */
85
85
  export type TranslationKeys<T extends object> = PathKeys<T>
86
+
87
+ // --- next-intl augmentation utilities ---
88
+ // Used in app global.d.ts to override useTranslations with type-safe keys.
89
+ // The standard use-intl AppConfig augmentation doesn't propagate through
90
+ // pnpm's nested node_modules, so apps override useTranslations directly.
91
+
92
+ /** All dot-separated paths (both leaves and namespaces) */
93
+ export type NestedKeyOf<T> = T extends object
94
+ ? { [K in keyof T & string]: K | `${K}.${NestedKeyOf<T[K]>}` }[keyof T & string]
95
+ : never
96
+
97
+ /** Resolve nested value by dot-separated path */
98
+ export type NestedValueOf<T, P extends string> = P extends `${infer K}.${infer R}`
99
+ ? K extends keyof T ? NestedValueOf<T[K], R> : never
100
+ : P extends keyof T ? T[P] : never
101
+
102
+ /** Paths that resolve to objects (valid useTranslations namespaces) */
103
+ export type NamespaceKeys<T, A extends string> = {
104
+ [K in A]: NestedValueOf<T, K> extends string ? never : K
105
+ }[A]
106
+
107
+ /** Paths that resolve to strings (valid t() keys) */
108
+ export type MessageKeys<T, A extends string> = {
109
+ [K in A]: NestedValueOf<T, K> extends string ? K : never
110
+ }[A]
111
+
112
+ /** Type-safe translator returned by useTranslations(namespace) */
113
+ export type IntlTranslator<Messages, NS extends NamespaceKeys<Messages, NestedKeyOf<Messages>>> = {
114
+ <K extends MessageKeys<NestedValueOf<Messages, NS>, NestedKeyOf<NestedValueOf<Messages, NS>>>>(
115
+ key: K,
116
+ values?: Record<string, unknown>,
117
+ ): string
118
+ rich<K extends MessageKeys<NestedValueOf<Messages, NS>, NestedKeyOf<NestedValueOf<Messages, NS>>>>(
119
+ key: K,
120
+ values?: Record<string, unknown>,
121
+ ): import('react').ReactNode
122
+ has(key: NestedKeyOf<NestedValueOf<Messages, NS>>): boolean
123
+ raw(key: NestedKeyOf<NestedValueOf<Messages, NS>>): unknown
124
+ }