@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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { I18nTranslations, PartialTranslations } from '../types'
|
|
2
|
+
|
|
3
|
+
type AnyObject = { [key: string]: unknown }
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Deep merge two objects
|
|
7
|
+
*/
|
|
8
|
+
function deepMerge(target: AnyObject, source: AnyObject): AnyObject {
|
|
9
|
+
const result = { ...target }
|
|
10
|
+
|
|
11
|
+
for (const key in source) {
|
|
12
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
13
|
+
const sourceValue = source[key]
|
|
14
|
+
const targetValue = result[key]
|
|
15
|
+
|
|
16
|
+
if (
|
|
17
|
+
sourceValue !== null &&
|
|
18
|
+
typeof sourceValue === 'object' &&
|
|
19
|
+
!Array.isArray(sourceValue) &&
|
|
20
|
+
targetValue !== null &&
|
|
21
|
+
typeof targetValue === 'object' &&
|
|
22
|
+
!Array.isArray(targetValue)
|
|
23
|
+
) {
|
|
24
|
+
// Recursively merge objects
|
|
25
|
+
result[key] = deepMerge(
|
|
26
|
+
targetValue as AnyObject,
|
|
27
|
+
sourceValue as AnyObject
|
|
28
|
+
)
|
|
29
|
+
} else if (sourceValue !== undefined) {
|
|
30
|
+
// Override with source value
|
|
31
|
+
result[key] = sourceValue
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Merge base translations with partial overrides or additional namespaces
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Override existing keys
|
|
44
|
+
* const customRu = mergeTranslations(ru, {
|
|
45
|
+
* ui: {
|
|
46
|
+
* select: {
|
|
47
|
+
* placeholder: 'Выберите марку авто...'
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* })
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Add app-specific namespace
|
|
54
|
+
* const appTranslations = mergeTranslations(en, {
|
|
55
|
+
* hub: {
|
|
56
|
+
* nav: { home: 'Home' }
|
|
57
|
+
* }
|
|
58
|
+
* })
|
|
59
|
+
*/
|
|
60
|
+
export function mergeTranslations<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
61
|
+
base: I18nTranslations,
|
|
62
|
+
overrides: PartialTranslations | T
|
|
63
|
+
): I18nTranslations & T {
|
|
64
|
+
return deepMerge(
|
|
65
|
+
base as unknown as AnyObject,
|
|
66
|
+
overrides as unknown as AnyObject
|
|
67
|
+
) as unknown as I18nTranslations & T
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create translations from multiple partial sources
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* const translations = createTranslations(
|
|
75
|
+
* en, // base
|
|
76
|
+
* uiOverrides, // UI customizations
|
|
77
|
+
* appStrings // App-specific strings
|
|
78
|
+
* )
|
|
79
|
+
*/
|
|
80
|
+
export function createTranslations(
|
|
81
|
+
base: I18nTranslations,
|
|
82
|
+
...overrides: PartialTranslations[]
|
|
83
|
+
): I18nTranslations {
|
|
84
|
+
return overrides.reduce<I18nTranslations>(
|
|
85
|
+
(acc, override) => mergeTranslations(acc, override),
|
|
86
|
+
base
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe translation keys
|
|
3
|
+
*
|
|
4
|
+
* Generates a union of all possible dot-notation paths from a nested object type.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* type Translations = {
|
|
9
|
+
* ui: {
|
|
10
|
+
* form: { save: string; cancel: string }
|
|
11
|
+
* select: { placeholder: string }
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* type Keys = PathKeys<Translations>
|
|
16
|
+
* // = "ui.form.save" | "ui.form.cancel" | "ui.select.placeholder"
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Recursively generates dot-notation paths for nested object types
|
|
22
|
+
*
|
|
23
|
+
* - For leaf values (string), returns the accumulated path
|
|
24
|
+
* - For nested objects, recursively builds paths
|
|
25
|
+
* - Limits depth to prevent infinite recursion with circular types
|
|
26
|
+
*/
|
|
27
|
+
export type PathKeys<T, Prefix extends string = '', Depth extends number[] = []> =
|
|
28
|
+
// Limit recursion depth to 10 levels (prevents infinite types)
|
|
29
|
+
Depth['length'] extends 10
|
|
30
|
+
? never
|
|
31
|
+
: T extends string
|
|
32
|
+
? Prefix
|
|
33
|
+
: T extends object
|
|
34
|
+
? {
|
|
35
|
+
[K in keyof T & string]: PathKeys<
|
|
36
|
+
T[K],
|
|
37
|
+
Prefix extends '' ? K : `${Prefix}.${K}`,
|
|
38
|
+
[...Depth, 0]
|
|
39
|
+
>
|
|
40
|
+
}[keyof T & string]
|
|
41
|
+
: never
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Type-safe translation function signature
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import type { TranslationFn, I18nTranslations } from '@djangocfg/i18n'
|
|
49
|
+
*
|
|
50
|
+
* // Type-safe t function
|
|
51
|
+
* const t: TranslationFn<I18nTranslations> = useT()
|
|
52
|
+
* t('ui.form.save') // OK
|
|
53
|
+
* t('ui.form.typo') // Error: Argument of type '"ui.form.typo"' is not assignable
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export type TranslationFn<T extends object> = {
|
|
57
|
+
(key: PathKeys<T>, params?: Record<string, string | number>): string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a type-safe translation function type for a specific translations object
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // In extension
|
|
66
|
+
* import type { TypedTranslationFn } from '@djangocfg/i18n'
|
|
67
|
+
* import type { LeadsTranslations } from './types'
|
|
68
|
+
*
|
|
69
|
+
* type LeadsT = TypedTranslationFn<{ leads: LeadsTranslations }>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export type TypedTranslationFn<T extends object> = TranslationFn<T>
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extracts the keys type from a translations object
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* import type { TranslationKeys, I18nTranslations } from '@djangocfg/i18n'
|
|
80
|
+
*
|
|
81
|
+
* type Keys = TranslationKeys<I18nTranslations>
|
|
82
|
+
* // = "ui.form.save" | "ui.select.placeholder" | ...
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export type TranslationKeys<T extends object> = PathKeys<T>
|