@umituz/react-native-localization 2.8.0 → 3.0.1
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/package.json +21 -11
- package/scripts/prepublish.js +29 -16
- package/src/domain/repositories/ILocalizationRepository.ts +2 -2
- package/src/index.ts +2 -1
- package/src/infrastructure/components/LanguageSwitcher.tsx +90 -37
- package/src/infrastructure/components/LocalizationProvider.tsx +115 -7
- package/src/infrastructure/components/__tests__/LanguageSwitcher.test.tsx +91 -0
- package/src/infrastructure/components/useLanguageNavigation.ts +4 -4
- package/src/infrastructure/config/TranslationCache.ts +27 -0
- package/src/infrastructure/config/TranslationLoader.ts +4 -8
- package/src/infrastructure/config/__tests__/TranslationCache.test.ts +44 -0
- package/src/infrastructure/config/__tests__/languagesData.test.ts +49 -0
- package/src/infrastructure/config/languages.ts +1 -1
- package/src/infrastructure/config/languagesData.ts +91 -61
- package/src/infrastructure/hooks/__tests__/useTranslation.test.ts +52 -0
- package/src/infrastructure/hooks/useLocalization.ts +58 -0
- package/src/infrastructure/hooks/useTranslation.ts +84 -29
- package/src/infrastructure/storage/LanguageInitializer.ts +7 -5
- package/src/infrastructure/storage/LanguageSwitcher.ts +3 -3
- package/src/infrastructure/storage/LocalizationStore.ts +103 -94
- package/src/infrastructure/storage/types/LocalizationState.ts +31 -0
- package/src/presentation/components/LanguageItem.tsx +109 -0
- package/src/presentation/components/SearchInput.tsx +90 -0
- package/src/presentation/components/__tests__/LanguageItem.test.tsx +106 -0
- package/src/presentation/components/__tests__/SearchInput.test.tsx +95 -0
- package/src/presentation/screens/LanguageSelectionScreen.tsx +90 -146
- package/src/presentation/screens/__tests__/LanguageSelectionScreen.test.tsx +166 -0
- package/src/scripts/prepublish.ts +48 -0
- package/src/infrastructure/locales/en-US/alerts.json +0 -107
- package/src/infrastructure/locales/en-US/auth.json +0 -34
- package/src/infrastructure/locales/en-US/branding.json +0 -8
- package/src/infrastructure/locales/en-US/clipboard.json +0 -9
- package/src/infrastructure/locales/en-US/common.json +0 -57
- package/src/infrastructure/locales/en-US/datetime.json +0 -138
- package/src/infrastructure/locales/en-US/device.json +0 -14
- package/src/infrastructure/locales/en-US/editor.json +0 -64
- package/src/infrastructure/locales/en-US/errors.json +0 -41
- package/src/infrastructure/locales/en-US/general.json +0 -57
- package/src/infrastructure/locales/en-US/goals.json +0 -5
- package/src/infrastructure/locales/en-US/haptics.json +0 -6
- package/src/infrastructure/locales/en-US/home.json +0 -62
- package/src/infrastructure/locales/en-US/index.ts +0 -54
- package/src/infrastructure/locales/en-US/navigation.json +0 -6
- package/src/infrastructure/locales/en-US/onboarding.json +0 -26
- package/src/infrastructure/locales/en-US/projects.json +0 -34
- package/src/infrastructure/locales/en-US/settings.json +0 -45
- package/src/infrastructure/locales/en-US/sharing.json +0 -8
- package/src/infrastructure/locales/en-US/templates.json +0 -28
- package/src/infrastructure/scripts/createLocaleLoaders.js +0 -177
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple tests for language registry
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { languageRegistry } from '../../config/languagesData';
|
|
6
|
+
|
|
7
|
+
describe('LanguageRegistry', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
languageRegistry.clearLanguages();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should get default language', () => {
|
|
13
|
+
const defaultLang = languageRegistry.getDefaultLanguage();
|
|
14
|
+
expect(defaultLang).toBeDefined();
|
|
15
|
+
expect(defaultLang.code).toBe('en-US');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should check if language is supported', () => {
|
|
19
|
+
const supported = languageRegistry.isLanguageSupported('en-US');
|
|
20
|
+
expect(supported).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should check if language is not supported', () => {
|
|
24
|
+
const supported = languageRegistry.isLanguageSupported('unknown');
|
|
25
|
+
expect(supported).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should get language by code', () => {
|
|
29
|
+
const language = languageRegistry.getLanguageByCode('en-US');
|
|
30
|
+
expect(language).toBeDefined();
|
|
31
|
+
expect(language?.code).toBe('en-US');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return undefined for unknown code', () => {
|
|
35
|
+
const language = languageRegistry.getLanguageByCode('unknown');
|
|
36
|
+
expect(language).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should search languages', () => {
|
|
40
|
+
const results = languageRegistry.searchLanguages('english');
|
|
41
|
+
expect(Array.isArray(results)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should get supported languages', () => {
|
|
45
|
+
const languages = languageRegistry.getLanguages();
|
|
46
|
+
expect(Array.isArray(languages)).toBe(true);
|
|
47
|
+
expect(languages.length).toBeGreaterThan(0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import * as Localization from 'expo-localization';
|
|
17
17
|
import { LANGUAGES as LANGUAGES_DATA } from './languagesData';
|
|
18
|
-
import { Language } from '
|
|
18
|
+
import type { Language } from '../storage/types/LocalizationState';
|
|
19
19
|
|
|
20
20
|
// Single source of truth for supported languages
|
|
21
21
|
export const SUPPORTED_LANGUAGES: Language[] = LANGUAGES_DATA;
|
|
@@ -1,81 +1,111 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Language Configuration
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Generated from App Factory language_config.yaml
|
|
3
|
+
* Generic language interface and utilities for localization packages
|
|
4
|
+
* This is a base configuration that can be extended by consuming applications
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
export interface Language {
|
|
9
8
|
code: string;
|
|
10
9
|
name: string;
|
|
11
10
|
nativeName: string;
|
|
12
|
-
flag
|
|
11
|
+
flag?: string;
|
|
12
|
+
isRTL?: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{ code: '
|
|
21
|
-
{ code: 'el-GR', name: 'Greek', nativeName: 'Ελληνικά', flag: '🇬🇷' },
|
|
22
|
-
{ code: 'en-AU', name: 'English (Australia)', nativeName: 'English', flag: '🇦🇺' },
|
|
23
|
-
{ code: 'en-CA', name: 'English (Canada)', nativeName: 'English', flag: '🇨🇦' },
|
|
24
|
-
{ code: 'en-GB', name: 'English (UK)', nativeName: 'English', flag: '🇬🇧' },
|
|
25
|
-
{ code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸' },
|
|
26
|
-
{ code: 'es-ES', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸' },
|
|
27
|
-
{ code: 'es-MX', name: 'Spanish (Mexico)', nativeName: 'Español', flag: '🇲🇽' },
|
|
28
|
-
{ code: 'fi-FI', name: 'Finnish', nativeName: 'Suomi', flag: '🇫🇮' },
|
|
29
|
-
{ code: 'fr-CA', name: 'French (Canada)', nativeName: 'Français', flag: '🇨🇦' },
|
|
30
|
-
{ code: 'fr-FR', name: 'French', nativeName: 'Français', flag: '🇫🇷' },
|
|
31
|
-
{ code: 'hi-IN', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳' },
|
|
32
|
-
{ code: 'hr-HR', name: 'Croatian', nativeName: 'Hrvatski', flag: '🇭🇷' },
|
|
33
|
-
{ code: 'hu-HU', name: 'Hungarian', nativeName: 'Magyar', flag: '🇭🇺' },
|
|
34
|
-
{ code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮🇩' },
|
|
35
|
-
{ code: 'it-IT', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹' },
|
|
36
|
-
{ code: 'ja-JP', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵' },
|
|
37
|
-
{ code: 'ko-KR', name: 'Korean', nativeName: '한국어', flag: '🇰🇷' },
|
|
38
|
-
{ code: 'ms-MY', name: 'Malay', nativeName: 'Bahasa Melayu', flag: '🇲🇾' },
|
|
39
|
-
{ code: 'nl-NL', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱' },
|
|
40
|
-
{ code: 'no-NO', name: 'Norwegian', nativeName: 'Norsk', flag: '🇳🇴' },
|
|
41
|
-
{ code: 'pl-PL', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱' },
|
|
42
|
-
{ code: 'pt-BR', name: 'Portuguese (Brazil)', nativeName: 'Português', flag: '🇧🇷' },
|
|
43
|
-
{ code: 'pt-PT', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹' },
|
|
44
|
-
{ code: 'ro-RO', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴' },
|
|
45
|
-
{ code: 'ru-RU', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺' },
|
|
46
|
-
{ code: 'sk-SK', name: 'Slovak', nativeName: 'Slovenčina', flag: '🇸🇰' },
|
|
47
|
-
{ code: 'sv-SE', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪' },
|
|
48
|
-
{ code: 'th-TH', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭' },
|
|
49
|
-
{ code: 'tl-PH', name: 'Filipino', nativeName: 'Filipino', flag: '🇵🇭' },
|
|
50
|
-
{ code: 'tr-TR', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷' },
|
|
51
|
-
{ code: 'uk-UA', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦' },
|
|
52
|
-
{ code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳' },
|
|
53
|
-
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳' },
|
|
54
|
-
{ code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文', flag: '🇹🇼' },
|
|
15
|
+
/**
|
|
16
|
+
* Default language configuration
|
|
17
|
+
* Applications can override this by providing their own language list
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_LANGUAGES: Language[] = [
|
|
20
|
+
{ code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸', isRTL: false },
|
|
55
21
|
];
|
|
56
22
|
|
|
57
23
|
/**
|
|
58
|
-
*
|
|
24
|
+
* Language registry for dynamic language management
|
|
59
25
|
*/
|
|
26
|
+
class LanguageRegistry {
|
|
27
|
+
private languages: Language[] = [...DEFAULT_LANGUAGES];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Register new languages
|
|
31
|
+
*/
|
|
32
|
+
registerLanguages(languages: Language[]): void {
|
|
33
|
+
this.languages = [...this.languages, ...languages];
|
|
34
|
+
if (__DEV__) {
|
|
35
|
+
console.log(`[Localization] Registered ${languages.length} languages`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get all registered languages
|
|
41
|
+
*/
|
|
42
|
+
getLanguages(): Language[] {
|
|
43
|
+
return [...this.languages];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Clear all languages (reset to default)
|
|
48
|
+
*/
|
|
49
|
+
clearLanguages(): void {
|
|
50
|
+
this.languages = [...DEFAULT_LANGUAGES];
|
|
51
|
+
if (__DEV__) {
|
|
52
|
+
console.log('[Localization] Cleared language registry');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get language by code
|
|
58
|
+
*/
|
|
59
|
+
getLanguageByCode(code: string): Language | undefined {
|
|
60
|
+
return this.languages.find(lang => lang.code === code);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Search languages by name or native name
|
|
65
|
+
*/
|
|
66
|
+
searchLanguages(query: string): Language[] {
|
|
67
|
+
const lowerQuery = query.toLowerCase();
|
|
68
|
+
return this.languages.filter(
|
|
69
|
+
lang =>
|
|
70
|
+
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
71
|
+
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if language is supported
|
|
77
|
+
*/
|
|
78
|
+
isLanguageSupported(code: string): boolean {
|
|
79
|
+
return this.languages.some(lang => lang.code === code);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get default language
|
|
84
|
+
*/
|
|
85
|
+
getDefaultLanguage(): Language {
|
|
86
|
+
return this.languages[0] || DEFAULT_LANGUAGES[0];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Singleton instance
|
|
91
|
+
export const languageRegistry = new LanguageRegistry();
|
|
92
|
+
|
|
93
|
+
// Export convenience functions that delegate to registry
|
|
60
94
|
export const getLanguageByCode = (code: string): Language | undefined => {
|
|
61
|
-
return
|
|
95
|
+
return languageRegistry.getLanguageByCode(code);
|
|
62
96
|
};
|
|
63
97
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*/
|
|
67
|
-
export const getDefaultLanguage = (): Language => {
|
|
68
|
-
return LANGUAGES.find(lang => lang.code === 'en-US')!;
|
|
98
|
+
export const searchLanguages = (query: string): Language[] => {
|
|
99
|
+
return languageRegistry.searchLanguages(query);
|
|
69
100
|
};
|
|
70
101
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*/
|
|
74
|
-
export const searchLanguages = (query: string): Language[] => {
|
|
75
|
-
const lowerQuery = query.toLowerCase();
|
|
76
|
-
return LANGUAGES.filter(
|
|
77
|
-
lang =>
|
|
78
|
-
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
79
|
-
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
80
|
-
);
|
|
102
|
+
export const isLanguageSupported = (code: string): boolean => {
|
|
103
|
+
return languageRegistry.isLanguageSupported(code);
|
|
81
104
|
};
|
|
105
|
+
|
|
106
|
+
export const getDefaultLanguage = (): Language => {
|
|
107
|
+
return languageRegistry.getDefaultLanguage();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Legacy exports for backward compatibility
|
|
111
|
+
export const LANGUAGES = languageRegistry.getLanguages();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple tests for useTranslation hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useTranslationFunction } from '../useTranslation';
|
|
6
|
+
|
|
7
|
+
// Mock React hooks
|
|
8
|
+
jest.mock('react', () => ({
|
|
9
|
+
useMemo: jest.fn((fn) => fn()),
|
|
10
|
+
useCallback: jest.fn((fn) => fn),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Mock i18next
|
|
14
|
+
jest.mock('../../config/i18n', () => ({
|
|
15
|
+
isInitialized: true,
|
|
16
|
+
t: jest.fn((key) => key),
|
|
17
|
+
hasResourceBundle: jest.fn(() => false),
|
|
18
|
+
language: 'en-US',
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe('useTranslationFunction', () => {
|
|
22
|
+
it('should return translation function', () => {
|
|
23
|
+
const { t, clearCache } = useTranslationFunction();
|
|
24
|
+
|
|
25
|
+
expect(typeof t).toBe('function');
|
|
26
|
+
expect(typeof clearCache).toBe('function');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return key when translation not found', () => {
|
|
30
|
+
const { t } = useTranslationFunction();
|
|
31
|
+
const result = t('test.key');
|
|
32
|
+
|
|
33
|
+
expect(result).toBe('test.key');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should handle translation options', () => {
|
|
37
|
+
const { t } = useTranslationFunction();
|
|
38
|
+
const result = t('test.key', { count: 1 });
|
|
39
|
+
|
|
40
|
+
expect(typeof result).toBe('string');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should clear cache', () => {
|
|
44
|
+
const { t, clearCache } = useTranslationFunction();
|
|
45
|
+
|
|
46
|
+
// Add to cache
|
|
47
|
+
t('test.key');
|
|
48
|
+
|
|
49
|
+
// Clear cache
|
|
50
|
+
expect(() => clearCache()).not.toThrow();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localization Hook
|
|
3
|
+
* Provides a clean interface to localization functionality
|
|
4
|
+
* Follows Single Responsibility Principle
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useCallback } from 'react';
|
|
8
|
+
import { useLocalizationStore } from '../storage/LocalizationStore';
|
|
9
|
+
import { useTranslationFunction } from './useTranslation';
|
|
10
|
+
import type { Language } from '../../domain/repositories/ILocalizationRepository';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hook to use localization functionality
|
|
14
|
+
* Provides current language, RTL state, language switching, and translation function
|
|
15
|
+
*/
|
|
16
|
+
export const useLocalization = () => {
|
|
17
|
+
const store = useLocalizationStore();
|
|
18
|
+
const { t } = useTranslationFunction();
|
|
19
|
+
|
|
20
|
+
const getCurrentLanguageObject = useCallback((): Language | undefined => {
|
|
21
|
+
return store.getCurrentLanguage();
|
|
22
|
+
}, [store]);
|
|
23
|
+
|
|
24
|
+
const handleSetLanguage = useCallback(async (languageCode: string) => {
|
|
25
|
+
await store.setLanguage(languageCode);
|
|
26
|
+
}, [store]);
|
|
27
|
+
|
|
28
|
+
const handleInitialize = useCallback(async () => {
|
|
29
|
+
await store.initialize();
|
|
30
|
+
}, [store]);
|
|
31
|
+
|
|
32
|
+
const isLanguageSupported = useCallback((code: string) => {
|
|
33
|
+
return store.isLanguageSupported(code);
|
|
34
|
+
}, [store]);
|
|
35
|
+
|
|
36
|
+
const getSupportedLanguages = useCallback(() => {
|
|
37
|
+
return store.getSupportedLanguages();
|
|
38
|
+
}, [store]);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
// Translation function
|
|
42
|
+
t,
|
|
43
|
+
|
|
44
|
+
// Current state
|
|
45
|
+
currentLanguage: store.currentLanguage,
|
|
46
|
+
currentLanguageObject: getCurrentLanguageObject(),
|
|
47
|
+
isRTL: store.isRTL,
|
|
48
|
+
isInitialized: store.isInitialized,
|
|
49
|
+
|
|
50
|
+
// Actions
|
|
51
|
+
setLanguage: handleSetLanguage,
|
|
52
|
+
initialize: handleInitialize,
|
|
53
|
+
|
|
54
|
+
// Utilities
|
|
55
|
+
isLanguageSupported,
|
|
56
|
+
supportedLanguages: getSupportedLanguages(),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Translation Hook
|
|
3
3
|
*
|
|
4
|
-
* Provides translation function with proper fallbacks
|
|
4
|
+
* Provides translation function with proper fallbacks and performance optimization
|
|
5
5
|
* - React i18next integration
|
|
6
|
-
* -
|
|
6
|
+
* - Memoized translation function
|
|
7
7
|
* - Type-safe translation function
|
|
8
8
|
* - Auto-namespace detection from dot notation
|
|
9
|
+
* - Performance optimizations
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import { useCallback, useMemo } from 'react';
|
|
11
13
|
import i18n from '../config/i18n';
|
|
14
|
+
import { TranslationCache } from '../config/TranslationCache';
|
|
15
|
+
|
|
16
|
+
export interface TranslationOptions {
|
|
17
|
+
count?: number;
|
|
18
|
+
ns?: string | string[];
|
|
19
|
+
defaultValue?: string;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const translationCache = new TranslationCache();
|
|
12
24
|
|
|
13
25
|
/**
|
|
14
26
|
* Hook for translation functionality
|
|
@@ -16,40 +28,83 @@ import i18n from '../config/i18n';
|
|
|
16
28
|
* - t('namespace:key.subkey') - explicit namespace
|
|
17
29
|
* - t('namespace.key.subkey') - auto-detected namespace (first segment before dot)
|
|
18
30
|
*/
|
|
19
|
-
export const useTranslationFunction = ()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
export const useTranslationFunction = () => {
|
|
32
|
+
const isInitialized = useMemo(() => i18n.isInitialized, []);
|
|
33
|
+
|
|
34
|
+
const translate = useCallback((key: string, options: TranslationOptions = {}): string => {
|
|
35
|
+
if (!isInitialized || typeof i18n.t !== 'function') {
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
console.warn(`[Localization] i18n not initialized, returning key: ${key}`);
|
|
38
|
+
}
|
|
39
|
+
return options.defaultValue || key;
|
|
23
40
|
}
|
|
24
41
|
|
|
42
|
+
// Create cache key
|
|
43
|
+
const cacheKey = `${key}:${JSON.stringify(options)}`;
|
|
44
|
+
|
|
45
|
+
// Check cache first
|
|
46
|
+
const cached = translationCache.get(cacheKey);
|
|
47
|
+
if (cached) {
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let result: string;
|
|
52
|
+
|
|
25
53
|
// If key already has namespace separator (:), use as-is
|
|
26
54
|
if (key.includes(':')) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
}
|
|
55
|
+
const tempResult = i18n.t(key, options);
|
|
56
|
+
result = typeof tempResult === 'string' ? tempResult : key;
|
|
57
|
+
} else {
|
|
58
|
+
// Auto-detect namespace from first dot segment
|
|
59
|
+
const firstDotIndex = key.indexOf('.');
|
|
60
|
+
if (firstDotIndex > 0) {
|
|
61
|
+
const potentialNamespace = key.substring(0, firstDotIndex);
|
|
62
|
+
const restOfKey = key.substring(firstDotIndex + 1);
|
|
63
|
+
|
|
64
|
+
// Check if this namespace exists in i18n resources
|
|
65
|
+
const hasNamespace = i18n.hasResourceBundle(i18n.language, potentialNamespace);
|
|
30
66
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
if (hasNamespace) {
|
|
68
|
+
const namespacedKey = `${potentialNamespace}:${restOfKey}`;
|
|
69
|
+
const namespacedResult = i18n.t(namespacedKey, options);
|
|
70
|
+
|
|
71
|
+
// If translation found (not same as key), use it
|
|
72
|
+
if (namespacedResult !== namespacedKey && namespacedResult !== restOfKey) {
|
|
73
|
+
result = typeof namespacedResult === 'string' ? namespacedResult : key;
|
|
74
|
+
} else {
|
|
75
|
+
// Fallback to original key
|
|
76
|
+
const fallbackResult = i18n.t(key, options);
|
|
77
|
+
result = typeof fallbackResult === 'string' ? fallbackResult : key;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// Fallback to original key
|
|
81
|
+
const tempResult = i18n.t(key, options);
|
|
82
|
+
result = typeof tempResult === 'string' ? tempResult : key;
|
|
47
83
|
}
|
|
84
|
+
} else {
|
|
85
|
+
// No dot, use as-is
|
|
86
|
+
const noDotResult = i18n.t(key, options);
|
|
87
|
+
result = typeof noDotResult === 'string' ? noDotResult : key;
|
|
48
88
|
}
|
|
49
89
|
}
|
|
50
90
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
53
|
-
|
|
91
|
+
// Convert to string and cache
|
|
92
|
+
const finalResult: string = typeof result === 'string' ? result : key;
|
|
93
|
+
translationCache.set(cacheKey, finalResult);
|
|
94
|
+
|
|
95
|
+
return finalResult;
|
|
96
|
+
}, [isInitialized]);
|
|
97
|
+
|
|
98
|
+
// Clear cache when language changes
|
|
99
|
+
const clearCache = useCallback(() => {
|
|
100
|
+
translationCache.clear();
|
|
101
|
+
if (__DEV__) {
|
|
102
|
+
console.log('[Localization] Translation cache cleared');
|
|
103
|
+
}
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
t: translate,
|
|
108
|
+
clearCache,
|
|
54
109
|
};
|
|
55
|
-
};
|
|
110
|
+
};
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import { storageRepository } from '@umituz/react-native-storage';
|
|
11
11
|
import i18n from '../config/i18n';
|
|
12
|
-
import {
|
|
12
|
+
import { languageRegistry, getDefaultLanguage } from '../config/languagesData';
|
|
13
|
+
import { getDeviceLocale } from '../config/languages';
|
|
13
14
|
|
|
14
15
|
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
16
|
+
const DEFAULT_LANGUAGE = 'en-US';
|
|
15
17
|
|
|
16
18
|
export class LanguageInitializer {
|
|
17
19
|
/**
|
|
@@ -47,15 +49,15 @@ export class LanguageInitializer {
|
|
|
47
49
|
languageCode: string;
|
|
48
50
|
isRTL: boolean;
|
|
49
51
|
}> {
|
|
50
|
-
const language = getLanguageByCode(languageCode);
|
|
51
|
-
const finalLanguageCode = language ? languageCode :
|
|
52
|
-
const finalLanguageObj = getLanguageByCode(finalLanguageCode);
|
|
52
|
+
const language = languageRegistry.getLanguageByCode(languageCode);
|
|
53
|
+
const finalLanguageCode = language ? languageCode : 'en-US';
|
|
54
|
+
const finalLanguageObj = languageRegistry.getLanguageByCode(finalLanguageCode);
|
|
53
55
|
|
|
54
56
|
await i18n.changeLanguage(finalLanguageCode);
|
|
55
57
|
|
|
56
58
|
return {
|
|
57
59
|
languageCode: finalLanguageCode,
|
|
58
|
-
isRTL: finalLanguageObj?.
|
|
60
|
+
isRTL: finalLanguageObj?.isRTL || false,
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { storageRepository } from '@umituz/react-native-storage';
|
|
10
10
|
import i18n from '../config/i18n';
|
|
11
|
-
import {
|
|
11
|
+
import { languageRegistry } from '../config/languagesData';
|
|
12
12
|
|
|
13
13
|
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
14
14
|
|
|
@@ -20,7 +20,7 @@ export class LanguageSwitcher {
|
|
|
20
20
|
languageCode: string;
|
|
21
21
|
isRTL: boolean;
|
|
22
22
|
}> {
|
|
23
|
-
const language = getLanguageByCode(languageCode);
|
|
23
|
+
const language = languageRegistry.getLanguageByCode(languageCode);
|
|
24
24
|
|
|
25
25
|
if (!language) {
|
|
26
26
|
throw new Error(`Unsupported language: ${languageCode}`);
|
|
@@ -31,7 +31,7 @@ export class LanguageSwitcher {
|
|
|
31
31
|
|
|
32
32
|
return {
|
|
33
33
|
languageCode,
|
|
34
|
-
isRTL: language.
|
|
34
|
+
isRTL: language.isRTL || false,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
}
|