@umituz/react-native-localization 2.6.0 → 2.8.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.
Files changed (43) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/package.json +2 -2
  4. package/src/domain/repositories/ILocalizationRepository.ts +0 -0
  5. package/src/index.ts +3 -0
  6. package/src/infrastructure/components/LanguageSwitcher.tsx +0 -0
  7. package/src/infrastructure/components/LocalizationProvider.tsx +11 -3
  8. package/src/infrastructure/components/useLanguageNavigation.ts +0 -0
  9. package/src/infrastructure/config/I18nInitializer.ts +13 -66
  10. package/src/infrastructure/config/NamespaceResolver.ts +36 -0
  11. package/src/infrastructure/config/ResourceBuilder.ts +43 -0
  12. package/src/infrastructure/config/TranslationLoader.ts +0 -0
  13. package/src/infrastructure/config/i18n.ts +0 -0
  14. package/src/infrastructure/config/languages.ts +0 -0
  15. package/src/infrastructure/config/languagesData.ts +0 -0
  16. package/src/infrastructure/hooks/TranslationHook.ts +0 -0
  17. package/src/infrastructure/hooks/useTranslation.ts +0 -0
  18. package/src/infrastructure/locales/en-US/alerts.json +0 -0
  19. package/src/infrastructure/locales/en-US/auth.json +0 -0
  20. package/src/infrastructure/locales/en-US/branding.json +0 -0
  21. package/src/infrastructure/locales/en-US/clipboard.json +0 -0
  22. package/src/infrastructure/locales/en-US/common.json +0 -0
  23. package/src/infrastructure/locales/en-US/datetime.json +0 -0
  24. package/src/infrastructure/locales/en-US/device.json +0 -0
  25. package/src/infrastructure/locales/en-US/editor.json +0 -0
  26. package/src/infrastructure/locales/en-US/errors.json +0 -0
  27. package/src/infrastructure/locales/en-US/general.json +0 -0
  28. package/src/infrastructure/locales/en-US/goals.json +0 -0
  29. package/src/infrastructure/locales/en-US/haptics.json +0 -0
  30. package/src/infrastructure/locales/en-US/home.json +0 -0
  31. package/src/infrastructure/locales/en-US/index.ts +0 -0
  32. package/src/infrastructure/locales/en-US/navigation.json +0 -0
  33. package/src/infrastructure/locales/en-US/onboarding.json +0 -0
  34. package/src/infrastructure/locales/en-US/projects.json +0 -0
  35. package/src/infrastructure/locales/en-US/settings.json +0 -0
  36. package/src/infrastructure/locales/en-US/sharing.json +0 -0
  37. package/src/infrastructure/locales/en-US/templates.json +0 -0
  38. package/src/infrastructure/scripts/createLocaleLoaders.js +0 -0
  39. package/src/infrastructure/storage/AsyncStorageWrapper.ts +0 -0
  40. package/src/infrastructure/storage/LanguageInitializer.ts +0 -0
  41. package/src/infrastructure/storage/LanguageSwitcher.ts +0 -0
  42. package/src/infrastructure/storage/LocalizationStore.ts +0 -0
  43. package/src/presentation/screens/LanguageSelectionScreen.tsx +204 -0
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "English-only localization system for React Native apps with i18n support",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -33,7 +33,7 @@
33
33
  "license": "MIT",
34
34
  "repository": {
35
35
  "type": "git",
36
- "url": "https://github.com/umituz/react-native-localization"
36
+ "url": "git+https://github.com/umituz/react-native-localization.git"
37
37
  },
38
38
  "dependencies": {
39
39
  "expo-localization": "~15.0.0",
package/src/index.ts CHANGED
@@ -26,5 +26,8 @@ export {
26
26
  searchLanguages,
27
27
  } from './infrastructure/config/languages';
28
28
 
29
+ // Presentation
30
+ export { LanguageSelectionScreen } from './presentation/screens/LanguageSelectionScreen';
31
+
29
32
  // Types
30
33
  export type { Language, ILocalizationRepository } from './domain/repositories/ILocalizationRepository';
@@ -1,21 +1,29 @@
1
1
  /**
2
2
  * LocalizationProvider Component
3
- * Initializes localization system on mount
3
+ * Initializes localization system with app translations
4
4
  */
5
5
 
6
6
  import React, { useEffect, ReactNode } from 'react';
7
7
  import { useLocalizationStore } from '../storage/LocalizationStore';
8
+ import { I18nInitializer } from '../config/I18nInitializer';
8
9
 
9
10
  interface LocalizationProviderProps {
10
11
  children: ReactNode;
12
+ translations: Record<string, any>;
13
+ defaultLanguage?: string;
11
14
  }
12
15
 
13
- export const LocalizationProvider: React.FC<LocalizationProviderProps> = ({ children }) => {
16
+ export const LocalizationProvider: React.FC<LocalizationProviderProps> = ({
17
+ children,
18
+ translations,
19
+ defaultLanguage = 'en-US',
20
+ }) => {
14
21
  const initialize = useLocalizationStore((state) => state.initialize);
15
22
 
16
23
  useEffect(() => {
24
+ I18nInitializer.initialize(translations, defaultLanguage);
17
25
  initialize();
18
- }, [initialize]);
26
+ }, [translations, defaultLanguage, initialize]);
19
27
 
20
28
  return <>{children}</>;
21
29
  };
@@ -8,70 +8,16 @@
8
8
  import i18n from 'i18next';
9
9
  import { initReactI18next } from 'react-i18next';
10
10
  import { DEFAULT_LANGUAGE } from './languages';
11
- import { TranslationLoader } from './TranslationLoader';
12
-
13
- const DEFAULT_NAMESPACE = 'common';
11
+ import { ResourceBuilder } from './ResourceBuilder';
12
+ import { NamespaceResolver } from './NamespaceResolver';
14
13
 
15
14
  export class I18nInitializer {
16
15
  private static reactI18nextInitialized = false;
17
- private static appTranslations: Record<string, any> = {};
18
-
19
- /**
20
- * Register app translations (call before initialize)
21
- */
22
- static registerAppTranslations(translations: Record<string, any>): void {
23
- this.appTranslations = translations;
24
- }
25
-
26
- /**
27
- * Build resources with package + registered app translations
28
- */
29
- private static buildResources(): Record<string, Record<string, any>> {
30
- const packageTranslations = TranslationLoader.loadPackageTranslations();
31
-
32
- const resources: Record<string, Record<string, any>> = {
33
- 'en-US': {},
34
- };
35
-
36
- const enUSPackage = packageTranslations['en-US'] || {};
37
-
38
- // Add package namespaces
39
- for (const [namespace, translations] of Object.entries(enUSPackage)) {
40
- resources['en-US'][namespace] = translations;
41
- }
42
16
 
43
- // Merge app translations (app overrides package)
44
- for (const [namespace, translations] of Object.entries(this.appTranslations)) {
45
- if (resources['en-US'][namespace]) {
46
- resources['en-US'][namespace] = TranslationLoader.mergeTranslations(
47
- resources['en-US'][namespace],
48
- translations
49
- );
50
- } else {
51
- resources['en-US'][namespace] = translations;
52
- }
53
- }
54
-
55
- return resources;
56
- }
57
-
58
- private static getNamespaces(): string[] {
59
- const packageTranslations = TranslationLoader.loadPackageTranslations();
60
- const enUSPackage = packageTranslations['en-US'] || {};
61
-
62
- const namespaces = new Set([
63
- ...Object.keys(enUSPackage),
64
- ...Object.keys(this.appTranslations),
65
- ]);
66
-
67
- if (!namespaces.has(DEFAULT_NAMESPACE)) {
68
- namespaces.add(DEFAULT_NAMESPACE);
69
- }
70
-
71
- return Array.from(namespaces);
72
- }
73
-
74
- static initialize(): void {
17
+ static initialize(
18
+ appTranslations: Record<string, any>,
19
+ languageCode: string = DEFAULT_LANGUAGE
20
+ ): void {
75
21
  if (i18n.isInitialized) {
76
22
  return;
77
23
  }
@@ -82,16 +28,17 @@ export class I18nInitializer {
82
28
  this.reactI18nextInitialized = true;
83
29
  }
84
30
 
85
- const resources = this.buildResources();
86
- const namespaces = this.getNamespaces();
31
+ const resources = ResourceBuilder.buildResources(appTranslations, languageCode);
32
+ const namespaces = NamespaceResolver.getNamespaces(appTranslations, languageCode);
33
+ const defaultNamespace = NamespaceResolver.getDefaultNamespace();
87
34
 
88
35
  i18n.init({
89
36
  resources,
90
- lng: DEFAULT_LANGUAGE,
91
- fallbackLng: DEFAULT_LANGUAGE,
37
+ lng: languageCode,
38
+ fallbackLng: languageCode,
92
39
  ns: namespaces,
93
- defaultNS: DEFAULT_NAMESPACE,
94
- fallbackNS: DEFAULT_NAMESPACE,
40
+ defaultNS: defaultNamespace,
41
+ fallbackNS: defaultNamespace,
95
42
  interpolation: { escapeValue: false },
96
43
  react: { useSuspense: false },
97
44
  compatibilityJSON: 'v3',
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Namespace Resolver
3
+ * Resolves available namespaces from translations
4
+ */
5
+
6
+ import { TranslationLoader } from './TranslationLoader';
7
+
8
+ const DEFAULT_NAMESPACE = 'common';
9
+
10
+ export class NamespaceResolver {
11
+ /**
12
+ * Get all available namespaces from package and app translations
13
+ */
14
+ static getNamespaces(
15
+ appTranslations: Record<string, any>,
16
+ languageCode: string
17
+ ): string[] {
18
+ const packageTranslations = TranslationLoader.loadPackageTranslations();
19
+ const packageLang = packageTranslations[languageCode] || {};
20
+
21
+ const namespaces = new Set([
22
+ ...Object.keys(packageLang),
23
+ ...Object.keys(appTranslations),
24
+ ]);
25
+
26
+ if (!namespaces.has(DEFAULT_NAMESPACE)) {
27
+ namespaces.add(DEFAULT_NAMESPACE);
28
+ }
29
+
30
+ return Array.from(namespaces);
31
+ }
32
+
33
+ static getDefaultNamespace(): string {
34
+ return DEFAULT_NAMESPACE;
35
+ }
36
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Resource Builder
3
+ * Builds i18n resources from package and app translations
4
+ */
5
+
6
+ import { TranslationLoader } from './TranslationLoader';
7
+
8
+ export class ResourceBuilder {
9
+ /**
10
+ * Build resources with package + app translations
11
+ */
12
+ static buildResources(
13
+ appTranslations: Record<string, any>,
14
+ languageCode: string
15
+ ): Record<string, Record<string, any>> {
16
+ const packageTranslations = TranslationLoader.loadPackageTranslations();
17
+
18
+ const resources: Record<string, Record<string, any>> = {
19
+ [languageCode]: {},
20
+ };
21
+
22
+ const packageLang = packageTranslations[languageCode] || {};
23
+
24
+ // Add package namespaces
25
+ for (const [namespace, translations] of Object.entries(packageLang)) {
26
+ resources[languageCode][namespace] = translations;
27
+ }
28
+
29
+ // Merge app translations (app overrides package)
30
+ for (const [namespace, translations] of Object.entries(appTranslations)) {
31
+ if (resources[languageCode][namespace]) {
32
+ resources[languageCode][namespace] = TranslationLoader.mergeTranslations(
33
+ resources[languageCode][namespace],
34
+ translations
35
+ );
36
+ } else {
37
+ resources[languageCode][namespace] = translations;
38
+ }
39
+ }
40
+
41
+ return resources;
42
+ }
43
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Language Selection Screen
3
+ *
4
+ * Language picker with search functionality
5
+ *
6
+ * App Factory - Universal Language Selector
7
+ */
8
+
9
+ import React, { useState, useMemo } from 'react';
10
+ import {
11
+ View,
12
+ StyleSheet,
13
+ FlatList,
14
+ TouchableOpacity,
15
+ TextInput,
16
+ } from 'react-native';
17
+ import { useNavigation } from '@react-navigation/native';
18
+ import { useAppDesignTokens, withAlpha, STATIC_TOKENS, type DesignTokens } from '@umituz/react-native-design-system-theme';
19
+ import { AtomicIcon, AtomicText } from '@umituz/react-native-design-system-atoms';
20
+ import { ScreenLayout } from '@umituz/react-native-design-system-organisms';
21
+ import { useLocalization, searchLanguages, Language, LANGUAGES } from '@umituz/react-native-localization';
22
+
23
+ /**
24
+ * Language Selection Screen Component
25
+ */
26
+ export const LanguageSelectionScreen: React.FC = () => {
27
+ const navigation = useNavigation();
28
+ const { t, currentLanguage, setLanguage } = useLocalization();
29
+ const tokens = useAppDesignTokens();
30
+ const [searchQuery, setSearchQuery] = useState('');
31
+ const [selectedCode, setSelectedCode] = useState(currentLanguage);
32
+ const [isFocused, setIsFocused] = useState(false);
33
+
34
+ const filteredLanguages = useMemo(() => {
35
+ return searchLanguages(searchQuery);
36
+ }, [searchQuery]);
37
+
38
+ const handleLanguageSelect = async (code: string) => {
39
+ setSelectedCode(code);
40
+ await setLanguage(code);
41
+ navigation.goBack();
42
+ };
43
+
44
+ const renderLanguageItem = ({ item }: { item: Language }) => {
45
+ const isSelected = selectedCode === item.code;
46
+
47
+ return (
48
+ <TouchableOpacity
49
+ style={StyleSheet.flatten([
50
+ styles.languageItem,
51
+ {
52
+ borderColor: isSelected
53
+ ? tokens.colors.primary
54
+ : tokens.colors.borderLight,
55
+ backgroundColor: isSelected
56
+ ? withAlpha(tokens.colors.primary, 0.1)
57
+ : tokens.colors.surface,
58
+ },
59
+ ])}
60
+ onPress={() => handleLanguageSelect(item.code)}
61
+ activeOpacity={0.7}
62
+ >
63
+ <View style={styles.languageContent}>
64
+ <AtomicText style={StyleSheet.flatten([STATIC_TOKENS.typography.headingLarge, styles.flag])}>
65
+ {item.flag}
66
+ </AtomicText>
67
+ <View style={styles.languageText}>
68
+ <AtomicText
69
+ style={StyleSheet.flatten([
70
+ STATIC_TOKENS.typography.bodyMedium,
71
+ styles.nativeName,
72
+ ])}
73
+ >
74
+ {item.nativeName}
75
+ </AtomicText>
76
+ <AtomicText style={StyleSheet.flatten([{ color: tokens.colors.textSecondary }])}>
77
+ {item.name}
78
+ </AtomicText>
79
+ </View>
80
+ </View>
81
+ {isSelected && (
82
+ <AtomicIcon
83
+ name="CircleCheck"
84
+ size="md"
85
+ color="primary"
86
+ />
87
+ )}
88
+ </TouchableOpacity>
89
+ );
90
+ };
91
+
92
+ return (
93
+ <ScreenLayout scrollable={false} testID="language-selection-screen">
94
+ {/* Search Input */}
95
+ <View
96
+ style={StyleSheet.flatten([
97
+ styles.searchContainer,
98
+ {
99
+ borderColor: isFocused ? tokens.colors.primary : tokens.colors.borderLight,
100
+ borderWidth: isFocused ? 2 : 1.5,
101
+ backgroundColor: tokens.colors.surface,
102
+ },
103
+ ])}
104
+ >
105
+ <AtomicIcon
106
+ name="Search"
107
+ size="md"
108
+ color="secondary"
109
+ style={styles.searchIcon}
110
+ />
111
+ <TextInput
112
+ style={StyleSheet.flatten([styles.searchInput, { color: tokens.colors.textPrimary }])}
113
+ placeholder={t('settings.languageSelection.searchPlaceholder')}
114
+ placeholderTextColor={tokens.colors.textSecondary}
115
+ value={searchQuery}
116
+ onChangeText={setSearchQuery}
117
+ onFocus={() => setIsFocused(true)}
118
+ onBlur={() => setIsFocused(false)}
119
+ autoCapitalize="none"
120
+ autoCorrect={false}
121
+ />
122
+ {searchQuery.length > 0 && (
123
+ <TouchableOpacity
124
+ onPress={() => setSearchQuery('')}
125
+ style={styles.clearButton}
126
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
127
+ >
128
+ <AtomicIcon
129
+ name="X"
130
+ size="sm"
131
+ color="secondary"
132
+ />
133
+ </TouchableOpacity>
134
+ )}
135
+ </View>
136
+
137
+ {/* Language List */}
138
+ <FlatList
139
+ data={filteredLanguages}
140
+ renderItem={renderLanguageItem}
141
+ keyExtractor={item => item.code}
142
+ contentContainerStyle={styles.listContent}
143
+ showsVerticalScrollIndicator={false}
144
+ keyboardShouldPersistTaps="handled"
145
+ />
146
+ </ScreenLayout>
147
+ );
148
+ };
149
+
150
+ const styles = StyleSheet.create({
151
+ searchContainer: {
152
+ flexDirection: 'row',
153
+ alignItems: 'center',
154
+ marginHorizontal: 20,
155
+ marginBottom: 24,
156
+ paddingHorizontal: 16,
157
+ paddingVertical: 8,
158
+ borderRadius: STATIC_TOKENS.borders.radius.lg,
159
+ },
160
+ searchIcon: {
161
+ marginRight: 8,
162
+ },
163
+ searchInput: {
164
+ flex: 1,
165
+ fontSize: STATIC_TOKENS.typography.bodyMedium.fontSize,
166
+ padding: 0,
167
+ fontWeight: '500',
168
+ },
169
+ clearButton: {
170
+ padding: STATIC_TOKENS.spacing.xs,
171
+ },
172
+ listContent: {
173
+ paddingHorizontal: 20,
174
+ paddingBottom: 32,
175
+ },
176
+ languageItem: {
177
+ flexDirection: 'row',
178
+ alignItems: 'center',
179
+ justifyContent: 'space-between',
180
+ padding: STATIC_TOKENS.spacing.md,
181
+ borderRadius: STATIC_TOKENS.borders.radius.lg,
182
+ borderWidth: 2,
183
+ marginBottom: 8,
184
+ },
185
+ languageContent: {
186
+ flexDirection: 'row',
187
+ alignItems: 'center',
188
+ flex: 1,
189
+ gap: 16,
190
+ },
191
+ flag: {
192
+ fontSize: STATIC_TOKENS.typography.headingLarge.fontSize,
193
+ },
194
+ languageText: {
195
+ flex: 1,
196
+ gap: 2,
197
+ },
198
+ nativeName: {
199
+ fontWeight: '600',
200
+ },
201
+ });
202
+
203
+ export default LanguageSelectionScreen;
204
+