@jsarc/intl 0.0.0
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 +596 -0
- package/config.ts +36 -0
- package/core/TranslationService.ts +153 -0
- package/core/types.ts +20 -0
- package/hooks/useTranslation.tsx +48 -0
- package/index.ts +4 -0
- package/js/config.js +14 -0
- package/js/core/TranslationService.js +104 -0
- package/js/core/types.js +1 -0
- package/js/hooks/useTranslation.jsx +40 -0
- package/js/index.js +1 -0
- package/js/providers/IntlProvider.jsx +106 -0
- package/js/utils/loaders.js +64 -0
- package/js/utils/mergeTranslations.js +16 -0
- package/js/utils.js +1 -0
- package/package.json +22 -0
- package/providers/IntlProvider.tsx +138 -0
- package/tsconfig.json +27 -0
- package/utils/loaders.ts +79 -0
- package/utils/mergeTranslations.ts +15 -0
- package/utils.ts +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { TranslationService } from '../core/TranslationService';
|
|
3
|
+
import { getSavedLocale, saveLocale, SUPPORTED_LOCALES, type TranslationsConfig } from '../config';
|
|
4
|
+
import type { Locale } from '../config';
|
|
5
|
+
|
|
6
|
+
const ArcIntlContext = createContext<ReturnType<typeof useArcIntlValue> | null>(null);
|
|
7
|
+
|
|
8
|
+
const useArcIntlValue = (
|
|
9
|
+
translationsConfig: TranslationsConfig,
|
|
10
|
+
supportedLocales: string[] = SUPPORTED_LOCALES
|
|
11
|
+
) => {
|
|
12
|
+
const [service] = useState(() => new TranslationService(supportedLocales, translationsConfig));
|
|
13
|
+
const [currentLocale, setCurrentLocale] = useState<Locale>(getSavedLocale(supportedLocales));
|
|
14
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
15
|
+
const [initialized, setInitialized] = useState(false);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const initialize = async () => {
|
|
19
|
+
setIsLoading(true);
|
|
20
|
+
try {
|
|
21
|
+
if (!initialized) {
|
|
22
|
+
await service.initialize(currentLocale);
|
|
23
|
+
setInitialized(true);
|
|
24
|
+
} else {
|
|
25
|
+
await service.initialize(currentLocale);
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('❌ Failed to initialize translations:', error);
|
|
29
|
+
} finally {
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
initialize();
|
|
36
|
+
}, 0);
|
|
37
|
+
|
|
38
|
+
return () => clearTimeout(timer);
|
|
39
|
+
}, [currentLocale, service, initialized]);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!initialized || isLoading || !translationsConfig.modules) return;
|
|
43
|
+
|
|
44
|
+
const loadAllModules = async () => {
|
|
45
|
+
const moduleNames = Object.keys(translationsConfig.modules || {});
|
|
46
|
+
|
|
47
|
+
for (const moduleName of moduleNames) {
|
|
48
|
+
try {
|
|
49
|
+
await service.loadModule(moduleName);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(`❌ Failed to load module ${moduleName}:`, error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
loadAllModules();
|
|
57
|
+
}, [initialized, translationsConfig.modules, service, isLoading]);
|
|
58
|
+
|
|
59
|
+
const changeLocale = useCallback(async (locale: Locale) => {
|
|
60
|
+
if (!supportedLocales.includes(locale)) {
|
|
61
|
+
console.warn(`⚠️ Locale ${locale} is not supported`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (locale === currentLocale) return;
|
|
66
|
+
|
|
67
|
+
setIsLoading(true);
|
|
68
|
+
try {
|
|
69
|
+
await service.initialize(locale);
|
|
70
|
+
setCurrentLocale(locale);
|
|
71
|
+
saveLocale(locale);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('❌ Failed to change locale:', error);
|
|
74
|
+
} finally {
|
|
75
|
+
setIsLoading(false);
|
|
76
|
+
}
|
|
77
|
+
}, [currentLocale, supportedLocales, service]);
|
|
78
|
+
|
|
79
|
+
const loadModuleTranslations = useCallback(async (moduleName: string) => {
|
|
80
|
+
setIsLoading(true);
|
|
81
|
+
try {
|
|
82
|
+
await service.loadModule(moduleName);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(`❌ Failed to load module ${moduleName}:`, error);
|
|
85
|
+
throw error;
|
|
86
|
+
} finally {
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
}
|
|
89
|
+
}, [service]);
|
|
90
|
+
|
|
91
|
+
const t = useCallback((key: string, params?: Record<string, any>, options?: any) => {
|
|
92
|
+
const result = service.t(key, params || {}, options || {});
|
|
93
|
+
return result;
|
|
94
|
+
}, [service]);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
t,
|
|
98
|
+
changeLocale,
|
|
99
|
+
currentLocale,
|
|
100
|
+
isLoading,
|
|
101
|
+
loadModuleTranslations,
|
|
102
|
+
isInitialized: initialized,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const ArcIntlProvider: React.FC<{
|
|
107
|
+
children: React.ReactNode
|
|
108
|
+
translations: TranslationsConfig
|
|
109
|
+
supportedLocales?: string[];
|
|
110
|
+
}> = ({
|
|
111
|
+
translations,
|
|
112
|
+
supportedLocales,
|
|
113
|
+
children,
|
|
114
|
+
}) => {
|
|
115
|
+
|
|
116
|
+
const value = useArcIntlValue(
|
|
117
|
+
translations,
|
|
118
|
+
(typeof supportedLocales === 'object' &&
|
|
119
|
+
!!Array.isArray(supportedLocales) &&
|
|
120
|
+
supportedLocales.length > 0)
|
|
121
|
+
? supportedLocales
|
|
122
|
+
: SUPPORTED_LOCALES
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return <ArcIntlContext.Provider value={value}>{children}</ArcIntlContext.Provider>;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const useArcIntl = () => {
|
|
129
|
+
const context = useContext(ArcIntlContext);
|
|
130
|
+
if (!context) throw new Error('useArcIntl must be used within an ArcIntlProvider');
|
|
131
|
+
return context;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export default {
|
|
135
|
+
useArcIntlValue,
|
|
136
|
+
ArcIntlProvider,
|
|
137
|
+
useArcIntl,
|
|
138
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable", "ESNext"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"noUncheckedSideEffectImports": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src/**/*"],
|
|
23
|
+
"exclude": [
|
|
24
|
+
"node_modules",
|
|
25
|
+
"**/*.d.ts"
|
|
26
|
+
]
|
|
27
|
+
}
|
package/utils/loaders.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { TranslationsConfig } from '../config';
|
|
2
|
+
|
|
3
|
+
export const loadBaseTranslations = async (
|
|
4
|
+
locale: string,
|
|
5
|
+
translationsConfig: TranslationsConfig
|
|
6
|
+
): Promise<Record<string, any>> => {
|
|
7
|
+
if (!translationsConfig?.base[locale]) {
|
|
8
|
+
console.warn(`❌ No base translations found for locale: ${locale}`);
|
|
9
|
+
console.warn('Available locales:', translationsConfig?.base ? Object.keys(translationsConfig.base) : 'none');
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const loader = translationsConfig.base[locale];
|
|
15
|
+
const result = await loader();
|
|
16
|
+
return result;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error(`❌ Failed to load base ${locale} translations`, error);
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const loadModulesTranslations = async (
|
|
24
|
+
locale: string,
|
|
25
|
+
translationsConfig: TranslationsConfig
|
|
26
|
+
): Promise<Array<{ moduleName: string; translations: Record<string, any> }>> => {
|
|
27
|
+
const results: any[] = [];
|
|
28
|
+
|
|
29
|
+
if (!translationsConfig?.modules) {
|
|
30
|
+
console.warn(`⚠️ No modules configuration found for locale "${locale}"`);
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const [moduleName, moduleTranslations] of Object.entries(translationsConfig.modules)) {
|
|
35
|
+
if (moduleTranslations[locale]) {
|
|
36
|
+
try {
|
|
37
|
+
const loader = moduleTranslations[locale];
|
|
38
|
+
const translations = await loader();
|
|
39
|
+
|
|
40
|
+
results.push({
|
|
41
|
+
moduleName,
|
|
42
|
+
translations
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`❌ Failed to load translations for module "${moduleName}" and locale "${locale}"`, error);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
console.warn(`⚠️ No translations for module "${moduleName}" in locale "${locale}"`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const loadModuleTranslations = async (
|
|
55
|
+
moduleName: string,
|
|
56
|
+
locale: string,
|
|
57
|
+
translationsConfig: TranslationsConfig
|
|
58
|
+
): Promise<{ moduleName: string; translations: Record<string, any> } | undefined> => {
|
|
59
|
+
if (!translationsConfig?.modules?.[moduleName]?.[locale]) {
|
|
60
|
+
console.warn(`❌ No translations config found for module "${moduleName}" and locale "${locale}"`);
|
|
61
|
+
console.warn('Available modules:', translationsConfig?.modules ? Object.keys(translationsConfig.modules) : 'none');
|
|
62
|
+
if (translationsConfig?.modules?.[moduleName]) {
|
|
63
|
+
console.warn(`Available locales for ${moduleName}:`, Object.keys(translationsConfig.modules[moduleName]));
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const loader = translationsConfig.modules[moduleName][locale];
|
|
70
|
+
const translations = await loader();
|
|
71
|
+
return {
|
|
72
|
+
moduleName,
|
|
73
|
+
translations
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`❌ Failed to load translations for module "${moduleName}" and locale "${locale}"`, error);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const mergeDeep = (target: any, source: any): any => {
|
|
2
|
+
if (typeof target !== 'object' || typeof source !== 'object') return source;
|
|
3
|
+
|
|
4
|
+
const output = { ...target };
|
|
5
|
+
for (const key in source) {
|
|
6
|
+
if (source.hasOwnProperty(key)) {
|
|
7
|
+
if (target.hasOwnProperty(key)) {
|
|
8
|
+
output[key] = mergeDeep(target[key], source[key]);
|
|
9
|
+
} else {
|
|
10
|
+
output[key] = source[key];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return output;
|
|
15
|
+
};
|
package/utils.ts
ADDED
|
File without changes
|