@umituz/react-native-localization 3.0.2 → 3.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "3.0.2",
4
- "description": "English-only localization system for React Native apps with i18n support",
3
+ "version": "3.1.2",
4
+ "description": "Generic localization system for React Native apps with i18n support",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "scripts": {
@@ -53,6 +53,7 @@
53
53
  "@babel/preset-env": "^7.28.5",
54
54
  "@babel/preset-react": "^7.28.5",
55
55
  "@babel/preset-typescript": "^7.28.5",
56
+ "@react-native-async-storage/async-storage": "^2.2.0",
56
57
  "@testing-library/jest-native": "^5.4.3",
57
58
  "@testing-library/react": "^16.3.0",
58
59
  "@testing-library/react-hooks": "^8.0.1",
@@ -85,4 +86,4 @@
85
86
  "README.md",
86
87
  "LICENSE"
87
88
  ]
88
- }
89
+ }
@@ -0,0 +1,56 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // Default path if not provided
5
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
6
+ const localesDir = path.resolve(process.cwd(), targetDir);
7
+
8
+ console.log(`Setting up languages in: ${localesDir}`);
9
+
10
+ if (!fs.existsSync(localesDir)) {
11
+ console.error(`❌ Locales directory not found: ${localesDir}`);
12
+ process.exit(1);
13
+ }
14
+
15
+ try {
16
+ // Find all language files (e.g., en-US.ts, tr-TR.ts)
17
+ const files = fs.readdirSync(localesDir)
18
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/))
19
+ .sort();
20
+
21
+ const imports = [];
22
+ const exports = [];
23
+
24
+ files.forEach(file => {
25
+ const code = file.replace('.ts', '');
26
+ // Remove hyphens for variable names (en-US -> enUS)
27
+ const varName = code.replace(/-([a-z0-9])/g, (g) => g[1].toUpperCase()).replace('-', '');
28
+
29
+ imports.push(`import ${varName} from "./${code}";`);
30
+ exports.push(` "${code}": ${varName},`);
31
+ });
32
+
33
+ const content = `/**
34
+ * Localization Index
35
+ * Exports all available translation files
36
+ * Auto-generated by scripts/setup-languages.js
37
+ */
38
+
39
+ ${imports.join('\n')}
40
+
41
+ export const translations = {
42
+ ${exports.join('\n')}
43
+ };
44
+
45
+ export type TranslationKey = keyof typeof translations;
46
+
47
+ export default translations;
48
+ `;
49
+
50
+ fs.writeFileSync(path.join(localesDir, 'index.ts'), content);
51
+ console.log(`✅ Generated index.ts with ${files.length} languages`);
52
+
53
+ } catch (error) {
54
+ console.error('❌ Error setting up languages:', error);
55
+ process.exit(1);
56
+ }
@@ -0,0 +1,6 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // Placeholder for translation script
5
+ console.log('🌍 [Localization] Translate Missing Script');
6
+ console.log('Not implemented yet. This script would check for missing keys against en-US and auto-translate them.');
@@ -45,5 +45,25 @@ describe('LanguageRegistry', () => {
45
45
  const languages = languageRegistry.getLanguages();
46
46
  expect(Array.isArray(languages)).toBe(true);
47
47
  expect(languages.length).toBeGreaterThan(0);
48
+ // Should now support many languages (29+)
49
+ expect(languages.length).toBeGreaterThan(20);
50
+ });
51
+
52
+ it('should support newly added languages', () => {
53
+ expect(languageRegistry.isLanguageSupported('cs-CZ')).toBe(true);
54
+ expect(languageRegistry.isLanguageSupported('pt-BR')).toBe(true);
55
+ expect(languageRegistry.isLanguageSupported('zh-TW')).toBe(true);
56
+ expect(languageRegistry.isLanguageSupported('el-GR')).toBe(true);
57
+ });
58
+
59
+ it('should find language attributes correctly', () => {
60
+ const czech = languageRegistry.getLanguageByCode('cs-CZ');
61
+ expect(czech).toBeDefined();
62
+ expect(czech?.name).toBe('Czech');
63
+ expect(czech?.flag).toBe('🇨🇿');
64
+
65
+ const brazil = languageRegistry.getLanguageByCode('pt-BR');
66
+ expect(brazil).toBeDefined();
67
+ expect(brazil?.name).toBe('Portuguese (Brazil)');
48
68
  });
49
69
  });
@@ -33,7 +33,7 @@ export const DEFAULT_LANGUAGE = 'en-US';
33
33
  * - All variants map to en-US (only supported language)
34
34
  */
35
35
  const LOCALE_MAPPING: Record<string, string> = {
36
- // All English variants map to en-US
36
+ // English variants map to en-US
37
37
  'en': 'en-US',
38
38
  'en-US': 'en-US',
39
39
  'en-GB': 'en-US',
@@ -42,85 +42,68 @@ const LOCALE_MAPPING: Record<string, string> = {
42
42
  'en-NZ': 'en-US',
43
43
  'en-IE': 'en-US',
44
44
  'en-ZA': 'en-US',
45
-
46
- // All other languages map to en-US (fallback)
47
- 'ar': 'en-US',
48
- 'ar-SA': 'en-US',
49
- 'ar-AE': 'en-US',
50
- 'ar-EG': 'en-US',
51
- 'bg': 'en-US',
52
- 'bg-BG': 'en-US',
53
- 'cs': 'en-US',
54
- 'cs-CZ': 'en-US',
55
- 'da': 'en-US',
56
- 'da-DK': 'en-US',
57
- 'de': 'en-US',
58
- 'de-DE': 'en-US',
59
- 'de-AT': 'en-US',
60
- 'de-CH': 'en-US',
61
- 'el': 'en-US',
62
- 'es': 'en-US',
63
- 'es-ES': 'en-US',
64
- 'es-MX': 'en-US',
65
- 'es-AR': 'en-US',
66
- 'fi': 'en-US',
67
- 'fi-FI': 'en-US',
68
- 'fr': 'en-US',
69
- 'fr-FR': 'en-US',
70
- 'fr-CA': 'en-US',
71
- 'fr-BE': 'en-US',
72
- 'fr-CH': 'en-US',
73
- 'hi': 'en-US',
74
- 'hi-IN': 'en-US',
75
- 'hr': 'en-US',
76
- 'hu': 'en-US',
77
- 'hu-HU': 'en-US',
78
- 'id': 'en-US',
79
- 'id-ID': 'en-US',
80
- 'it': 'en-US',
81
- 'it-IT': 'en-US',
82
- 'ja': 'en-US',
83
- 'ja-JP': 'en-US',
84
- 'ko': 'en-US',
85
- 'ko-KR': 'en-US',
86
- 'ms': 'en-US',
87
- 'ms-MY': 'en-US',
88
- 'nb': 'en-US',
89
- 'no': 'en-US',
90
- 'no-NO': 'en-US',
91
- 'nl': 'en-US',
92
- 'nl-NL': 'en-US',
93
- 'nl-BE': 'en-US',
94
- 'pl': 'en-US',
95
- 'pl-PL': 'en-US',
96
- 'pt': 'en-US',
97
- 'pt-PT': 'en-US',
98
- 'pt-BR': 'en-US',
99
- 'ro': 'en-US',
100
- 'ro-RO': 'en-US',
101
- 'ru': 'en-US',
102
- 'ru-RU': 'en-US',
103
- 'sk': 'en-US',
104
- 'sv': 'en-US',
105
- 'sv-SE': 'en-US',
106
- 'th': 'en-US',
107
- 'th-TH': 'en-US',
108
- 'tl': 'en-US',
109
- 'tl-PH': 'en-US',
110
- 'fil': 'en-US',
111
- 'tr': 'en-US',
112
- 'tr-TR': 'en-US',
113
- 'uk': 'en-US',
114
- 'uk-UA': 'en-US',
115
- 'vi': 'en-US',
116
- 'vi-VN': 'en-US',
117
- 'zh': 'en-US',
118
- 'zh-CN': 'en-US',
119
- 'zh-Hans': 'en-US',
120
- 'zh-Hans-CN': 'en-US',
121
- 'zh-Hant': 'en-US',
122
- 'zh-TW': 'en-US',
123
- 'zh-HK': 'en-US',
45
+ 'en-SG': 'en-US',
46
+ 'en-IN': 'en-US',
47
+
48
+ // Portuguese mappings
49
+ 'pt': 'pt-PT', // Default to European Portuguese if ambiguous, or use pt-BR if preferred.
50
+ 'pt-BR': 'pt-BR', // Now natively supported
51
+ 'pt-PT': 'pt-PT',
52
+
53
+ // Spanish variants
54
+ 'es': 'es-ES',
55
+ 'es-ES': 'es-ES',
56
+ 'es-MX': 'es-ES', // Fallback to ES until MX is explicitly added
57
+ 'es-AR': 'es-ES',
58
+ 'es-US': 'es-ES', // Common in US
59
+
60
+ // French variants
61
+ 'fr': 'fr-FR',
62
+ 'fr-FR': 'fr-FR',
63
+ 'fr-CA': 'fr-FR', // Fallback to FR until CA is explicitly added
64
+ 'fr-BE': 'fr-FR',
65
+ 'fr-CH': 'fr-FR',
66
+
67
+ // Norwegian (Bokmål to no-NO)
68
+ 'no': 'no-NO',
69
+ 'nb': 'no-NO',
70
+ 'nn': 'no-NO', // Nynorsk fallback
71
+
72
+ // Chinese variants
73
+ 'zh': 'zh-CN',
74
+ 'zh-CN': 'zh-CN',
75
+ 'zh-Hans': 'zh-CN',
76
+ 'zh-Hans-CN': 'zh-CN',
77
+ 'zh-Hant': 'zh-TW', // Map Traditional to zh-TW
78
+ 'zh-TW': 'zh-TW',
79
+ 'zh-HK': 'zh-TW', // Map HK to TW for Traditional
80
+
81
+ // Others
82
+ 'ar': 'ar-SA',
83
+ 'bg': 'bg-BG',
84
+ 'cs': 'cs-CZ',
85
+ 'da': 'da-DK',
86
+ 'de': 'de-DE',
87
+ 'el': 'el-GR', // Greek
88
+ 'fi': 'fi-FI',
89
+ 'hi': 'hi-IN',
90
+ 'hr': 'hr-HR', // Croatian
91
+ 'hu': 'hu-HU',
92
+ 'id': 'id-ID',
93
+ 'it': 'it-IT',
94
+ 'ja': 'ja-JP',
95
+ 'ko': 'ko-KR',
96
+ 'ms': 'ms-MY',
97
+ 'nl': 'nl-NL',
98
+ 'pl': 'pl-PL',
99
+ 'ro': 'ro-RO',
100
+ 'ru': 'ru-RU',
101
+ 'sk': 'sk-SK', // Slovak
102
+ 'sv': 'sv-SE',
103
+ 'th': 'th-TH',
104
+ 'tr': 'tr-TR',
105
+ 'uk': 'uk-UA',
106
+ 'vi': 'vi-VN',
124
107
  };
125
108
 
126
109
  export const getLanguageByCode = (code: string): Language | undefined => {
@@ -17,7 +17,40 @@ export interface Language {
17
17
  * Applications can override this by providing their own language list
18
18
  */
19
19
  export const DEFAULT_LANGUAGES: Language[] = [
20
+ { code: 'ar-SA', name: 'Arabic', nativeName: 'العربية', flag: '🇸🇦', isRTL: true },
21
+ { code: 'bg-BG', name: 'Bulgarian', nativeName: 'Български', flag: '🇧🇬', isRTL: false },
22
+ { code: 'cs-CZ', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿', isRTL: false },
23
+ { code: 'da-DK', name: 'Danish', nativeName: 'Dansk', flag: '🇩🇰', isRTL: false },
24
+ { code: 'de-DE', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪', isRTL: false },
25
+ { code: 'el-GR', name: 'Greek', nativeName: 'Ελληνικά', flag: '🇬🇷', isRTL: false },
20
26
  { code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸', isRTL: false },
27
+ { code: 'es-ES', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸', isRTL: false },
28
+ { code: 'fi-FI', name: 'Finnish', nativeName: 'Suomi', flag: '🇫🇮', isRTL: false },
29
+ { code: 'fr-FR', name: 'French', nativeName: 'Français', flag: '🇫🇷', isRTL: false },
30
+ { code: 'hi-IN', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳', isRTL: false },
31
+ { code: 'hr-HR', name: 'Croatian', nativeName: 'Hrvatski', flag: '🇭🇷', isRTL: false },
32
+ { code: 'hu-HU', name: 'Hungarian', nativeName: 'Magyar', flag: '🇭🇺', isRTL: false },
33
+ { code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮', isRTL: false },
34
+ { code: 'it-IT', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹', isRTL: false },
35
+ { code: 'ja-JP', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵', isRTL: false },
36
+ { code: 'ko-KR', name: 'Korean', nativeName: '한국어', flag: '🇰🇷', isRTL: false },
37
+ { code: 'ms-MY', name: 'Malay', nativeName: 'Bahasa Melayu', flag: '🇲🇾', isRTL: false },
38
+ { code: 'nl-NL', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱', isRTL: false },
39
+ { code: 'no-NO', name: 'Norwegian', nativeName: 'Norsk', flag: '🇳🇴', isRTL: false },
40
+ { code: 'pl-PL', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱', isRTL: false },
41
+ { code: 'pt-BR', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)', flag: '🇧🇷', isRTL: false },
42
+ { code: 'pt-PT', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹', isRTL: false },
43
+ { code: 'ro-RO', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴', isRTL: false },
44
+ { code: 'ru-RU', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺', isRTL: false },
45
+ { code: 'sk-SK', name: 'Slovak', nativeName: 'Slovenčina', flag: '🇸🇰', isRTL: false },
46
+ { code: 'sv-SE', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪', isRTL: false },
47
+ { code: 'th-TH', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭', isRTL: false },
48
+ { code: 'tl-PH', name: 'Filipino', nativeName: 'Filipino', flag: '🇵🇭', isRTL: false },
49
+ { code: 'tr-TR', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷', isRTL: false },
50
+ { code: 'uk-UA', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦', isRTL: false },
51
+ { code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳', isRTL: false },
52
+ { code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳', isRTL: false },
53
+ { code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文', flag: '🇹🇼', isRTL: false },
21
54
  ];
22
55
 
23
56
  /**
@@ -81,9 +114,11 @@ class LanguageRegistry {
81
114
 
82
115
  /**
83
116
  * Get default language
117
+ * Prioritizes en-US, otherwise returns first available
84
118
  */
85
119
  getDefaultLanguage(): Language {
86
- return this.languages[0] || DEFAULT_LANGUAGES[0];
120
+ const en = this.languages.find(l => l.code === 'en-US');
121
+ return en || this.languages[0] || DEFAULT_LANGUAGES[0];
87
122
  }
88
123
  }
89
124
 
@@ -23,7 +23,7 @@ export class LanguageSwitcher {
23
23
  const language = languageRegistry.getLanguageByCode(languageCode);
24
24
 
25
25
  if (!language) {
26
- throw new Error(`Unsupported language: ${languageCode}`);
26
+ console.warn(`[LanguageSwitcher] Language ${languageCode} not found in registry, proceeding anyway.`);
27
27
  }
28
28
 
29
29
  await i18n.changeLanguage(languageCode);
@@ -31,7 +31,7 @@ export class LanguageSwitcher {
31
31
 
32
32
  return {
33
33
  languageCode,
34
- isRTL: language.isRTL || false,
34
+ isRTL: language?.isRTL || false,
35
35
  };
36
36
  }
37
37
  }
@@ -5,17 +5,17 @@
5
5
  import React from 'react';
6
6
  import { render, fireEvent } from '@testing-library/react-native';
7
7
  import { LanguageSelectionScreen } from '../LanguageSelectionScreen';
8
- import { useLocalization } from '../../infrastructure/hooks/useLocalization';
9
- import { searchLanguages } from '../../infrastructure/config/languagesData';
8
+ import { useLocalization } from '../../../infrastructure/hooks/useLocalization';
9
+ import { searchLanguages } from '../../../infrastructure/config/languages';
10
10
 
11
11
  // Mock dependencies
12
- jest.mock('../../infrastructure/hooks/useLocalization');
13
- jest.mock('../../infrastructure/config/languagesData');
12
+ jest.mock('../../../infrastructure/hooks/useLocalization');
13
+ jest.mock('../../../infrastructure/config/languages');
14
14
  jest.mock('@react-navigation/native', () => ({
15
15
  useNavigation: () => ({
16
16
  goBack: jest.fn(),
17
17
  }),
18
- }));
18
+ }), { virtual: true });
19
19
 
20
20
  const mockUseLocalization = useLocalization as jest.MockedFunction<typeof useLocalization>;
21
21
  const mockSearchLanguages = searchLanguages as jest.MockedFunction<typeof searchLanguages>;
@@ -23,7 +23,7 @@ const mockSearchLanguages = searchLanguages as jest.MockedFunction<typeof search
23
23
  const mockLanguage = {
24
24
  code: 'en-US',
25
25
  name: 'English',
26
- nativeName: 'English',
26
+ nativeName: 'Native English',
27
27
  flag: '🇺🇸',
28
28
  isRTL: false,
29
29
  };
@@ -34,7 +34,7 @@ describe('LanguageSelectionScreen', () => {
34
34
 
35
35
  beforeEach(() => {
36
36
  jest.clearAllMocks();
37
-
37
+
38
38
  mockUseLocalization.mockReturnValue({
39
39
  currentLanguage: 'en-US',
40
40
  setLanguage: mockSetLanguage,
@@ -90,7 +90,7 @@ describe('LanguageSelectionScreen', () => {
90
90
 
91
91
  it('should use custom render function when provided', () => {
92
92
  const customRender = jest.fn().mockReturnValue(<div>Custom Item</div>);
93
-
93
+
94
94
  render(
95
95
  <LanguageSelectionScreen
96
96
  searchPlaceholder="Search languages..."
@@ -107,7 +107,7 @@ describe('LanguageSelectionScreen', () => {
107
107
 
108
108
  it('should use custom search input when provided', () => {
109
109
  const customSearchInput = jest.fn().mockReturnValue(<div>Custom Search</div>);
110
-
110
+
111
111
  render(
112
112
  <LanguageSelectionScreen
113
113
  searchPlaceholder="Search languages..."
@@ -126,7 +126,7 @@ describe('LanguageSelectionScreen', () => {
126
126
  const CustomContainer = ({ children }: { children: React.ReactNode }) => (
127
127
  <div testID="custom-container">{children}</div>
128
128
  );
129
-
129
+
130
130
  const { getByTestId } = render(
131
131
  <LanguageSelectionScreen
132
132
  searchPlaceholder="Search languages..."