@djangocfg/i18n 2.1.111
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 +235 -0
- package/package.json +70 -0
- package/src/context.tsx +293 -0
- package/src/index.ts +28 -0
- package/src/locales/en.ts +491 -0
- package/src/locales/index.ts +3 -0
- package/src/locales/ko.ts +491 -0
- package/src/locales/ru.ts +491 -0
- package/src/types.ts +656 -0
- package/src/utils/get-value.ts +30 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/interpolate.ts +23 -0
- package/src/utils/merge.ts +88 -0
- package/src/utils/path-keys.ts +85 -0
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# @djangocfg/i18n
|
|
2
|
+
|
|
3
|
+
Lightweight, type-safe i18n library for @djangocfg component packages. Provides built-in translations for English, Russian, and Korean with easy extensibility.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe** - Full TypeScript support with autocomplete for translation keys
|
|
8
|
+
- **Lightweight** - No heavy dependencies, pure React
|
|
9
|
+
- **Extensible** - Easy to add custom translations or override defaults
|
|
10
|
+
- **Works standalone** - Components work without provider using English defaults
|
|
11
|
+
- **3 languages built-in** - English, Russian, Korean
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @djangocfg/i18n
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { I18nProvider, useT, ru } from '@djangocfg/i18n'
|
|
23
|
+
|
|
24
|
+
// Wrap your app (optional - works without provider too)
|
|
25
|
+
<I18nProvider locale="ru" translations={ru}>
|
|
26
|
+
<App />
|
|
27
|
+
</I18nProvider>
|
|
28
|
+
|
|
29
|
+
// Use in components - simplest way
|
|
30
|
+
function MyComponent() {
|
|
31
|
+
const t = useT()
|
|
32
|
+
return <span>{t('ui.form.save')}</span>
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Hooks
|
|
37
|
+
|
|
38
|
+
| Hook | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `useT()` | Returns translation function, works with/without provider (recommended) |
|
|
41
|
+
| `useI18n()` | Full context: `{ t, locale, setLocale, translations }` |
|
|
42
|
+
| `useTranslation()` | Alias for `useT()` |
|
|
43
|
+
| `useLocale()` | Returns current locale string |
|
|
44
|
+
| `useTypedT<T>()` | Type-safe translation function with compile-time key validation |
|
|
45
|
+
|
|
46
|
+
### useT() - Recommended
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
function MyComponent() {
|
|
50
|
+
const t = useT()
|
|
51
|
+
return <button>{t('ui.form.save')}</button>
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### useI18n() - Full Context
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
function LocaleSwitcher() {
|
|
59
|
+
const { t, locale, setLocale } = useI18n()
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
|
|
63
|
+
<option value="en">English</option>
|
|
64
|
+
<option value="ru">Russian</option>
|
|
65
|
+
<option value="ko">Korean</option>
|
|
66
|
+
</select>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### useTypedT<T>() - Type-Safe Keys
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useTypedT } from '@djangocfg/i18n'
|
|
75
|
+
import type { I18nTranslations } from '@djangocfg/i18n'
|
|
76
|
+
|
|
77
|
+
function MyComponent() {
|
|
78
|
+
const t = useTypedT<I18nTranslations>()
|
|
79
|
+
return <span>{t('ui.form.save')}</span> // OK
|
|
80
|
+
// t('ui.form.typo') // Compile error!
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### getT() - Non-Hook Context
|
|
85
|
+
|
|
86
|
+
For class components or outside React:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { getT } from '@djangocfg/i18n'
|
|
90
|
+
|
|
91
|
+
const label = getT('ui.form.save')
|
|
92
|
+
const message = getT('ui.pagination.showing', { from: 1, to: 10, total: 100 })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Extending Translations
|
|
96
|
+
|
|
97
|
+
### mergeTranslations()
|
|
98
|
+
|
|
99
|
+
Deep merge base translations with custom overrides. Supports generic type parameter for app-specific namespaces:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { mergeTranslations, ru } from '@djangocfg/i18n'
|
|
103
|
+
|
|
104
|
+
// Override specific keys
|
|
105
|
+
const customRu = mergeTranslations(ru, {
|
|
106
|
+
ui: {
|
|
107
|
+
select: { placeholder: 'Выберите марку авто...' }
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Add app-specific namespace with type safety
|
|
112
|
+
type AppNamespace = { app: { title: string; welcome: string } }
|
|
113
|
+
|
|
114
|
+
const appRu = mergeTranslations<AppNamespace>(ru, {
|
|
115
|
+
app: {
|
|
116
|
+
title: 'Мое приложение',
|
|
117
|
+
welcome: 'Добро пожаловать!'
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
// Result type: I18nTranslations & AppNamespace
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### createTranslations()
|
|
124
|
+
|
|
125
|
+
Create translations from multiple partial sources:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { createTranslations, en } from '@djangocfg/i18n'
|
|
129
|
+
|
|
130
|
+
const translations = createTranslations(
|
|
131
|
+
en, // base
|
|
132
|
+
uiOverrides, // UI customizations
|
|
133
|
+
appStrings // App-specific strings
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Built-in Locales
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { en, ru, ko } from '@djangocfg/i18n'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- `en` - English (default)
|
|
144
|
+
- `ru` - Russian
|
|
145
|
+
- `ko` - Korean
|
|
146
|
+
|
|
147
|
+
## Interpolation
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
t('ui.pagination.showing', { from: 1, to: 10, total: 100 })
|
|
151
|
+
// => "1-10 of 100"
|
|
152
|
+
|
|
153
|
+
t('ui.select.moreItems', { count: 5 })
|
|
154
|
+
// => "+5 more"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Type Utilities
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import type { PathKeys, I18nKeys, I18nTranslations } from '@djangocfg/i18n'
|
|
161
|
+
|
|
162
|
+
// Get all valid keys from any translations type
|
|
163
|
+
type MyKeys = PathKeys<MyTranslations>
|
|
164
|
+
// = "form.title" | "form.submit" | "messages.error" | ...
|
|
165
|
+
|
|
166
|
+
// Built-in keys for base translations
|
|
167
|
+
const key: I18nKeys = 'ui.form.save' // OK
|
|
168
|
+
const bad: I18nKeys = 'ui.form.typo' // Error
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Translation Key Paths
|
|
172
|
+
|
|
173
|
+
All translation keys use dot notation:
|
|
174
|
+
|
|
175
|
+
### UI Components
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
ui.select.placeholder - "Select option..."
|
|
179
|
+
ui.select.search - "Search..."
|
|
180
|
+
ui.select.noResults - "No results found."
|
|
181
|
+
ui.select.selectAll - "Select all"
|
|
182
|
+
ui.select.clearAll - "Clear all"
|
|
183
|
+
ui.select.moreItems - "+{count} more"
|
|
184
|
+
|
|
185
|
+
ui.pagination.previous - "Previous"
|
|
186
|
+
ui.pagination.next - "Next"
|
|
187
|
+
ui.pagination.showing - "{from}-{to} of {total}"
|
|
188
|
+
|
|
189
|
+
ui.form.submit - "Submit"
|
|
190
|
+
ui.form.save - "Save"
|
|
191
|
+
ui.form.cancel - "Cancel"
|
|
192
|
+
ui.form.delete - "Delete"
|
|
193
|
+
ui.form.loading - "Loading..."
|
|
194
|
+
ui.form.required - "This field is required"
|
|
195
|
+
|
|
196
|
+
ui.dialog.close - "Close"
|
|
197
|
+
ui.dialog.confirm - "Confirm"
|
|
198
|
+
|
|
199
|
+
ui.errors.generic - "Something went wrong"
|
|
200
|
+
ui.errors.network - "Network error..."
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Layouts
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
layouts.sidebar.toggle - "Toggle sidebar"
|
|
207
|
+
layouts.profile.settings - "Settings"
|
|
208
|
+
layouts.profile.logout - "Log out"
|
|
209
|
+
layouts.theme.light - "Light"
|
|
210
|
+
layouts.theme.dark - "Dark"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### API
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
api.errors.networkError - "Network error"
|
|
217
|
+
api.status.connecting - "Connecting..."
|
|
218
|
+
api.status.connected - "Connected"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Works Without Provider
|
|
222
|
+
|
|
223
|
+
Components using `useT()` or `useI18n()` work without provider - they fall back to English defaults. This allows gradual adoption in existing projects.
|
|
224
|
+
|
|
225
|
+
## Integration with @djangocfg packages
|
|
226
|
+
|
|
227
|
+
This package is designed to work with:
|
|
228
|
+
- `@djangocfg/ui-core` - Base React components
|
|
229
|
+
- `@djangocfg/ui-nextjs` - Next.js specific components
|
|
230
|
+
- `@djangocfg/layouts` - Layout components
|
|
231
|
+
- `@djangocfg/nextjs` - Next.js App Router integration with next-intl
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@djangocfg/i18n",
|
|
3
|
+
"version": "2.1.111",
|
|
4
|
+
"description": "Lightweight i18n library for @djangocfg packages with built-in translations for English, Russian, and Korean",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"i18n",
|
|
7
|
+
"internationalization",
|
|
8
|
+
"localization",
|
|
9
|
+
"translations",
|
|
10
|
+
"react",
|
|
11
|
+
"nextjs",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "DjangoCFG",
|
|
16
|
+
"url": "https://djangocfg.com"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://djangocfg.com",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/markolofsen/django-cfg.git",
|
|
22
|
+
"directory": "packages/i18n"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/markolofsen/django-cfg/issues"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"main": "./src/index.ts",
|
|
29
|
+
"module": "./src/index.ts",
|
|
30
|
+
"types": "./src/index.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./src/index.ts",
|
|
34
|
+
"import": "./src/index.ts",
|
|
35
|
+
"require": "./src/index.ts"
|
|
36
|
+
},
|
|
37
|
+
"./locales": {
|
|
38
|
+
"types": "./src/locales/index.ts",
|
|
39
|
+
"import": "./src/locales/index.ts",
|
|
40
|
+
"require": "./src/locales/index.ts"
|
|
41
|
+
},
|
|
42
|
+
"./locales/*": "./src/locales/*.ts",
|
|
43
|
+
"./utils": {
|
|
44
|
+
"types": "./src/utils/index.ts",
|
|
45
|
+
"import": "./src/utils/index.ts",
|
|
46
|
+
"require": "./src/utils/index.ts"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"src",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"lint": "eslint .",
|
|
56
|
+
"check": "tsc --noEmit"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@djangocfg/typescript-config": "^2.1.111",
|
|
63
|
+
"@types/react": "^19.1.0",
|
|
64
|
+
"eslint": "^9.37.0",
|
|
65
|
+
"typescript": "^5.9.3"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
|
|
5
|
+
import { en } from './locales/en'
|
|
6
|
+
import type {
|
|
7
|
+
I18nContextValue,
|
|
8
|
+
I18nProviderProps,
|
|
9
|
+
I18nTranslations,
|
|
10
|
+
LocaleCode,
|
|
11
|
+
} from './types'
|
|
12
|
+
import { getNestedValue, interpolate, mergeTranslations } from './utils'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* I18n Context
|
|
16
|
+
*/
|
|
17
|
+
const I18nContext = React.createContext<I18nContextValue | null>(null)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default translations (English)
|
|
21
|
+
*/
|
|
22
|
+
const defaultTranslations: I18nTranslations = en
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* I18n Provider
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Basic usage with single locale
|
|
29
|
+
* import { I18nProvider, ru } from '@djangocfg/i18n'
|
|
30
|
+
*
|
|
31
|
+
* <I18nProvider locale="ru" translations={ru}>
|
|
32
|
+
* <App />
|
|
33
|
+
* </I18nProvider>
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // With all translations for dynamic switching
|
|
37
|
+
* import { I18nProvider, en, ru, ko } from '@djangocfg/i18n'
|
|
38
|
+
*
|
|
39
|
+
* <I18nProvider
|
|
40
|
+
* locale={currentLocale}
|
|
41
|
+
* allTranslations={{ en, ru, ko }}
|
|
42
|
+
* onLocaleChange={(locale) => setCurrentLocale(locale)}
|
|
43
|
+
* >
|
|
44
|
+
* <App />
|
|
45
|
+
* </I18nProvider>
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // With custom overrides
|
|
49
|
+
* import { I18nProvider, ru, mergeTranslations } from '@djangocfg/i18n'
|
|
50
|
+
*
|
|
51
|
+
* const customRu = mergeTranslations(ru, {
|
|
52
|
+
* ui: { select: { placeholder: 'Выберите марку...' } }
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* <I18nProvider locale="ru" translations={customRu}>
|
|
56
|
+
* <App />
|
|
57
|
+
* </I18nProvider>
|
|
58
|
+
*/
|
|
59
|
+
export function I18nProvider({
|
|
60
|
+
children,
|
|
61
|
+
locale: initialLocale = 'en',
|
|
62
|
+
translations: initialTranslations,
|
|
63
|
+
allTranslations,
|
|
64
|
+
onLocaleChange,
|
|
65
|
+
}: I18nProviderProps) {
|
|
66
|
+
const [locale, setLocaleState] = React.useState<LocaleCode>(initialLocale)
|
|
67
|
+
|
|
68
|
+
// Resolve current translations
|
|
69
|
+
const translations = React.useMemo<I18nTranslations>(() => {
|
|
70
|
+
// If allTranslations provided, use it to get translations for current locale
|
|
71
|
+
if (allTranslations && allTranslations[locale]) {
|
|
72
|
+
const localeTranslations = allTranslations[locale]
|
|
73
|
+
// Merge with defaults to fill any missing keys
|
|
74
|
+
return mergeTranslations(defaultTranslations, localeTranslations)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If single translations provided, use them
|
|
78
|
+
if (initialTranslations) {
|
|
79
|
+
return mergeTranslations(
|
|
80
|
+
defaultTranslations,
|
|
81
|
+
initialTranslations as I18nTranslations
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback to defaults
|
|
86
|
+
return defaultTranslations
|
|
87
|
+
}, [locale, allTranslations, initialTranslations])
|
|
88
|
+
|
|
89
|
+
// Translation function
|
|
90
|
+
const t = React.useCallback(
|
|
91
|
+
(key: string, params?: Record<string, string | number>): string => {
|
|
92
|
+
const value = getNestedValue(
|
|
93
|
+
translations as unknown as Record<string, unknown>,
|
|
94
|
+
key
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if (value === undefined) {
|
|
98
|
+
// Development warning
|
|
99
|
+
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
|
100
|
+
console.warn(`[i18n] Missing translation key: "${key}"`)
|
|
101
|
+
}
|
|
102
|
+
// Return the key as fallback
|
|
103
|
+
return key
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return interpolate(value, params)
|
|
107
|
+
},
|
|
108
|
+
[translations]
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Locale change handler
|
|
112
|
+
const setLocale = React.useCallback(
|
|
113
|
+
(newLocale: LocaleCode) => {
|
|
114
|
+
setLocaleState(newLocale)
|
|
115
|
+
onLocaleChange?.(newLocale)
|
|
116
|
+
},
|
|
117
|
+
[onLocaleChange]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// Sync with external locale changes
|
|
121
|
+
React.useEffect(() => {
|
|
122
|
+
if (initialLocale !== locale) {
|
|
123
|
+
setLocaleState(initialLocale)
|
|
124
|
+
}
|
|
125
|
+
}, [initialLocale])
|
|
126
|
+
|
|
127
|
+
const value = React.useMemo<I18nContextValue>(
|
|
128
|
+
() => ({
|
|
129
|
+
locale,
|
|
130
|
+
translations,
|
|
131
|
+
t,
|
|
132
|
+
setLocale,
|
|
133
|
+
}),
|
|
134
|
+
[locale, translations, t, setLocale]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Hook to access i18n context
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* function MyComponent() {
|
|
145
|
+
* const { t, locale, setLocale } = useI18n()
|
|
146
|
+
*
|
|
147
|
+
* return (
|
|
148
|
+
* <div>
|
|
149
|
+
* <p>{t('ui.select.placeholder')}</p>
|
|
150
|
+
* <p>{t('ui.pagination.showing', { from: 1, to: 10, total: 100 })}</p>
|
|
151
|
+
* <button onClick={() => setLocale('ru')}>Switch to Russian</button>
|
|
152
|
+
* </div>
|
|
153
|
+
* )
|
|
154
|
+
* }
|
|
155
|
+
*/
|
|
156
|
+
export function useI18n(): I18nContextValue {
|
|
157
|
+
const context = React.useContext(I18nContext)
|
|
158
|
+
|
|
159
|
+
if (!context) {
|
|
160
|
+
// Return default implementation if no provider
|
|
161
|
+
// This allows components to work without provider (using English defaults)
|
|
162
|
+
return {
|
|
163
|
+
locale: 'en',
|
|
164
|
+
translations: defaultTranslations,
|
|
165
|
+
t: (key: string, params?: Record<string, string | number>) => {
|
|
166
|
+
const value = getNestedValue(
|
|
167
|
+
defaultTranslations as unknown as Record<string, unknown>,
|
|
168
|
+
key
|
|
169
|
+
)
|
|
170
|
+
if (value === undefined) return key
|
|
171
|
+
return interpolate(value, params)
|
|
172
|
+
},
|
|
173
|
+
setLocale: () => {
|
|
174
|
+
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
|
175
|
+
console.warn(
|
|
176
|
+
'[i18n] setLocale called but no I18nProvider found. Wrap your app with I18nProvider to enable locale switching.'
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return context
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Hook to get the translation function only
|
|
188
|
+
* Useful when you only need translations without other context values
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* function MyComponent() {
|
|
192
|
+
* const t = useTranslation()
|
|
193
|
+
* return <span>{t('ui.form.save')}</span>
|
|
194
|
+
* }
|
|
195
|
+
*/
|
|
196
|
+
export function useTranslation() {
|
|
197
|
+
const { t } = useI18n()
|
|
198
|
+
return t
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Hook to get current locale
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* function MyComponent() {
|
|
206
|
+
* const locale = useLocale()
|
|
207
|
+
* return <span>Current: {locale}</span>
|
|
208
|
+
* }
|
|
209
|
+
*/
|
|
210
|
+
export function useLocale(): LocaleCode {
|
|
211
|
+
const { locale } = useI18n()
|
|
212
|
+
return locale
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Alias for useTranslation
|
|
217
|
+
* Lightweight hook that just returns the t function
|
|
218
|
+
* Works with or without I18nProvider (falls back to English)
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* function MyComponent() {
|
|
222
|
+
* const t = useT()
|
|
223
|
+
* return <span>{t('ui.form.save')}</span>
|
|
224
|
+
* }
|
|
225
|
+
*/
|
|
226
|
+
export const useT = useTranslation
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Type-safe translation hook
|
|
230
|
+
*
|
|
231
|
+
* Returns a translation function that only accepts valid keys from your translations type.
|
|
232
|
+
* Use this when you want compile-time checking of translation keys.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* import { useTypedT } from '@djangocfg/i18n'
|
|
237
|
+
* import type { I18nTranslations } from '@djangocfg/i18n'
|
|
238
|
+
*
|
|
239
|
+
* function MyComponent() {
|
|
240
|
+
* const t = useTypedT<I18nTranslations>()
|
|
241
|
+
* return <span>{t('ui.form.save')}</span> // OK
|
|
242
|
+
* return <span>{t('ui.form.typo')}</span> // Compile error!
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* // With merged translations (app + extensions)
|
|
249
|
+
* import type { AppTranslations } from '@/i18n/types'
|
|
250
|
+
*
|
|
251
|
+
* function MyComponent() {
|
|
252
|
+
* const t = useTypedT<AppTranslations>()
|
|
253
|
+
* return <span>{t('leads.form.title')}</span> // OK if leads is in AppTranslations
|
|
254
|
+
* }
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export function useTypedT<T extends object>(): (
|
|
258
|
+
key: import('./utils/path-keys').PathKeys<T>,
|
|
259
|
+
params?: Record<string, string | number>
|
|
260
|
+
) => string {
|
|
261
|
+
const { t } = useI18n()
|
|
262
|
+
return t as (
|
|
263
|
+
key: import('./utils/path-keys').PathKeys<T>,
|
|
264
|
+
params?: Record<string, string | number>
|
|
265
|
+
) => string
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get translation without hook (for class components or non-React contexts)
|
|
270
|
+
* Always uses English defaults, no context support
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* // In a class component
|
|
274
|
+
* render() {
|
|
275
|
+
* const title = getT('layouts.errors.somethingWentWrong')
|
|
276
|
+
* return <h1>{title}</h1>
|
|
277
|
+
* }
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* // With interpolation
|
|
281
|
+
* const message = getT('ui.pagination.showing', { from: 1, to: 10, total: 100 })
|
|
282
|
+
*/
|
|
283
|
+
export function getT(
|
|
284
|
+
key: string,
|
|
285
|
+
params?: Record<string, string | number>
|
|
286
|
+
): string {
|
|
287
|
+
const value = getNestedValue(
|
|
288
|
+
defaultTranslations as unknown as Record<string, unknown>,
|
|
289
|
+
key
|
|
290
|
+
)
|
|
291
|
+
if (value === undefined) return key
|
|
292
|
+
return interpolate(value, params)
|
|
293
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Context & Hooks
|
|
2
|
+
export { I18nProvider, useI18n, useTranslation, useLocale, useT, useTypedT, getT } from './context'
|
|
3
|
+
|
|
4
|
+
// Types
|
|
5
|
+
export type {
|
|
6
|
+
I18nTranslations,
|
|
7
|
+
PartialTranslations,
|
|
8
|
+
LocaleCode,
|
|
9
|
+
I18nContextValue,
|
|
10
|
+
I18nProviderProps,
|
|
11
|
+
DeepPartial,
|
|
12
|
+
I18nKeys,
|
|
13
|
+
I18nTranslationFn,
|
|
14
|
+
} from './types'
|
|
15
|
+
|
|
16
|
+
// Type-safe keys utilities
|
|
17
|
+
export type {
|
|
18
|
+
PathKeys,
|
|
19
|
+
TranslationFn,
|
|
20
|
+
TypedTranslationFn,
|
|
21
|
+
TranslationKeys,
|
|
22
|
+
} from './utils/path-keys'
|
|
23
|
+
|
|
24
|
+
// Locales
|
|
25
|
+
export { en, ru, ko } from './locales'
|
|
26
|
+
|
|
27
|
+
// Utils
|
|
28
|
+
export { interpolate, mergeTranslations, createTranslations } from './utils'
|