@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 +4 -3
- package/scripts/setup-languages.js +56 -0
- package/scripts/translate-missing.js +6 -0
- package/src/infrastructure/config/__tests__/languagesData.test.ts +20 -0
- package/src/infrastructure/config/languages.ts +63 -80
- package/src/infrastructure/config/languagesData.ts +36 -1
- package/src/infrastructure/storage/LanguageSwitcher.ts +2 -2
- package/src/presentation/screens/__tests__/LanguageSelectionScreen.test.tsx +10 -10
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-localization",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
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
|
-
//
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'ro': '
|
|
100
|
-
'
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 '
|
|
9
|
-
import { searchLanguages } from '
|
|
8
|
+
import { useLocalization } from '../../../infrastructure/hooks/useLocalization';
|
|
9
|
+
import { searchLanguages } from '../../../infrastructure/config/languages';
|
|
10
10
|
|
|
11
11
|
// Mock dependencies
|
|
12
|
-
jest.mock('
|
|
13
|
-
jest.mock('
|
|
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..."
|