@umituz/react-native-localization 3.5.1 → 3.5.4

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "3.5.1",
3
+ "version": "3.5.4",
4
4
  "description": "Generic localization system for React Native apps with i18n support",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -10,13 +10,13 @@
10
10
  "test": "jest",
11
11
  "test:watch": "jest --watch",
12
12
  "test:coverage": "jest --coverage",
13
- "prepublishOnly": "node scripts/prepublish.js",
13
+ "prepublishOnly": "node src/scripts/prepublish.js",
14
14
  "version:patch": "npm version patch -m 'chore: release v%s'",
15
15
  "version:minor": "npm version minor -m 'chore: release v%s'",
16
16
  "version:major": "npm version major -m 'chore: release v%s'",
17
- "i18n:setup": "node scripts/setup-languages.js",
18
- "i18n:sync": "node scripts/sync-translations.js",
19
- "i18n:translate": "node scripts/translate-missing.js"
17
+ "i18n:setup": "node src/scripts/setup-languages.js",
18
+ "i18n:sync": "node src/scripts/sync-translations.js",
19
+ "i18n:translate": "node src/scripts/translate-missing.js"
20
20
  },
21
21
  "keywords": [
22
22
  "react-native",
@@ -90,7 +90,6 @@
90
90
  },
91
91
  "files": [
92
92
  "src",
93
- "scripts",
94
93
  "README.md",
95
94
  "LICENSE"
96
95
  ]
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pre-Publish Script
5
+ * Basic checks before publishing
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
12
+ const SRC_DIR = path.join(PACKAGE_ROOT, 'src');
13
+
14
+ if (!fs.existsSync(SRC_DIR)) {
15
+ process.exit(1);
16
+ }
17
+
18
+ const mainFiles = [
19
+ 'src/index.ts',
20
+ 'src/infrastructure/config/i18n.ts',
21
+ 'src/infrastructure/storage/LocalizationStore.ts',
22
+ ];
23
+
24
+ let allFilesExist = true;
25
+ for (const file of mainFiles) {
26
+ const filePath = path.join(PACKAGE_ROOT, file);
27
+ if (!fs.existsSync(filePath)) {
28
+ allFilesExist = false;
29
+ }
30
+ }
31
+
32
+ if (!allFilesExist) {
33
+ process.exit(1);
34
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Setup Languages Script
5
+ * Generates index.ts with all available translation files
6
+ * Usage: node setup-languages.js [locales-dir]
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ function main() {
13
+ const targetDir = process.argv[2] || 'src/domains/localization/translations';
14
+ const localesDir = path.resolve(process.cwd(), targetDir);
15
+
16
+ if (!fs.existsSync(localesDir)) {
17
+ process.exit(1);
18
+ }
19
+
20
+ const files = fs.readdirSync(localesDir)
21
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/))
22
+ .sort();
23
+
24
+ const imports = [];
25
+ const exports = [];
26
+
27
+ files.forEach(file => {
28
+ const code = file.replace('.ts', '');
29
+ const varName = code.replace(/-([a-z0-9])/g, (g) => g[1].toUpperCase()).replace('-', '');
30
+
31
+ imports.push(`import ${varName} from "./${code}";`);
32
+ exports.push(` "${code}": ${varName},`);
33
+ });
34
+
35
+ const content = `/**
36
+ * Localization Index
37
+ * Exports all available translation files
38
+ * Auto-generated by scripts/setup-languages.js
39
+ */
40
+
41
+ ${imports.join('\n')}
42
+
43
+ export const translations = {
44
+ ${exports.join('\n')}
45
+ };
46
+
47
+ export type TranslationKey = keyof typeof translations;
48
+
49
+ export default translations;
50
+ `;
51
+
52
+ fs.writeFileSync(path.join(localesDir, 'index.ts'), content);
53
+ }
54
+
55
+ main();
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Sync Translations Script
5
+ * Synchronizes translation keys from en-US.ts to all other language files
6
+ * Usage: node sync-translations.js [locales-dir]
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { parseTypeScriptFile, generateTypeScriptContent } = require('./utils/file-parser');
12
+ const { addMissingKeys, removeExtraKeys } = require('./utils/sync-helper');
13
+
14
+ function syncLanguageFile(enUSPath, targetPath, langCode) {
15
+ const enUS = parseTypeScriptFile(enUSPath);
16
+ let target;
17
+
18
+ try {
19
+ target = parseTypeScriptFile(targetPath);
20
+ } catch {
21
+ target = {};
22
+ }
23
+
24
+ const addStats = { added: 0 };
25
+ const removeStats = { removed: 0 };
26
+
27
+ addMissingKeys(enUS, target, addStats);
28
+ removeExtraKeys(enUS, target, removeStats);
29
+
30
+ const changed = addStats.added > 0 || removeStats.removed > 0;
31
+
32
+ if (changed) {
33
+ const content = generateTypeScriptContent(target, langCode);
34
+ fs.writeFileSync(targetPath, content);
35
+ }
36
+
37
+ return {
38
+ added: addStats.added,
39
+ removed: removeStats.removed,
40
+ changed,
41
+ };
42
+ }
43
+
44
+ function main() {
45
+ const targetDir = process.argv[2] || 'src/domains/localization/translations';
46
+ const localesDir = path.resolve(process.cwd(), targetDir);
47
+
48
+ if (!fs.existsSync(localesDir)) {
49
+ process.exit(1);
50
+ }
51
+
52
+ const enUSPath = path.join(localesDir, 'en-US.ts');
53
+ if (!fs.existsSync(enUSPath)) {
54
+ process.exit(1);
55
+ }
56
+
57
+ const files = fs.readdirSync(localesDir)
58
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
59
+ .sort();
60
+
61
+ for (const file of files) {
62
+ const langCode = file.replace('.ts', '');
63
+ const targetPath = path.join(localesDir, file);
64
+ syncLanguageFile(enUSPath, targetPath, langCode);
65
+ }
66
+ }
67
+
68
+ main();
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Translate Missing Script
5
+ * Translates missing strings from en-US.ts to all other language files
6
+ * Usage: node translate-missing.js [locales-dir]
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { getTargetLanguage, isEnglishVariant } = require('./utils/translation-config');
12
+ const { parseTypeScriptFile, generateTypeScriptContent } = require('./utils/file-parser');
13
+ const { translateObject } = require('./utils/translator');
14
+
15
+ async function translateLanguageFile(enUSPath, targetPath, langCode) {
16
+ const targetLang = getTargetLanguage(langCode);
17
+
18
+ if (!targetLang) {
19
+ return 0;
20
+ }
21
+
22
+ if (isEnglishVariant(langCode)) {
23
+ return 0;
24
+ }
25
+
26
+ const enUS = parseTypeScriptFile(enUSPath);
27
+ let target;
28
+
29
+ try {
30
+ target = parseTypeScriptFile(targetPath);
31
+ } catch {
32
+ target = {};
33
+ }
34
+
35
+ const stats = { count: 0 };
36
+ await translateObject(enUS, target, targetLang, '', stats);
37
+
38
+ if (stats.count > 0) {
39
+ const content = generateTypeScriptContent(target, langCode);
40
+ fs.writeFileSync(targetPath, content);
41
+ }
42
+
43
+ return stats.count;
44
+ }
45
+
46
+ async function main() {
47
+ const targetDir = process.argv[2] || 'src/domains/localization/translations';
48
+ const localesDir = path.resolve(process.cwd(), targetDir);
49
+
50
+ if (!fs.existsSync(localesDir)) {
51
+ process.exit(1);
52
+ }
53
+
54
+ const enUSPath = path.join(localesDir, 'en-US.ts');
55
+ if (!fs.existsSync(enUSPath)) {
56
+ process.exit(1);
57
+ }
58
+
59
+ const files = fs.readdirSync(localesDir)
60
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
61
+ .sort();
62
+
63
+ let totalTranslated = 0;
64
+
65
+ for (const file of files) {
66
+ const langCode = file.replace('.ts', '');
67
+ const targetPath = path.join(localesDir, file);
68
+ const count = await translateLanguageFile(enUSPath, targetPath, langCode);
69
+ totalTranslated += count;
70
+ }
71
+ }
72
+
73
+ main().catch(() => {
74
+ process.exit(1);
75
+ });
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * File Parser
5
+ * Parse and generate TypeScript translation files
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const { getLangDisplayName } = require('./translation-config');
10
+
11
+ function parseTypeScriptFile(filePath) {
12
+ const content = fs.readFileSync(filePath, 'utf8');
13
+ const match = content.match(/export\s+default\s+(\{[\s\S]*\});?\s*$/);
14
+
15
+ if (!match) {
16
+ throw new Error(`Could not parse TypeScript file: ${filePath}`);
17
+ }
18
+
19
+ const objectStr = match[1].replace(/;$/, '');
20
+
21
+ try {
22
+ return eval(`(${objectStr})`);
23
+ } catch (error) {
24
+ throw new Error(`Failed to parse object in ${filePath}: ${error.message}`);
25
+ }
26
+ }
27
+
28
+ function stringifyValue(value, indent = 2) {
29
+ if (typeof value === 'string') {
30
+ const escaped = value
31
+ .replace(/\\/g, '\\\\')
32
+ .replace(/"/g, '\\"')
33
+ .replace(/\n/g, '\\n');
34
+ return `"${escaped}"`;
35
+ }
36
+
37
+ if (Array.isArray(value)) {
38
+ if (value.length === 0) return '[]';
39
+ const items = value.map(v => stringifyValue(v, indent + 2));
40
+ return `[${items.join(', ')}]`;
41
+ }
42
+
43
+ if (typeof value === 'object' && value !== null) {
44
+ const entries = Object.entries(value);
45
+
46
+ if (entries.length === 0) {
47
+ return '{}';
48
+ }
49
+
50
+ const spaces = ' '.repeat(indent);
51
+ const innerSpaces = ' '.repeat(indent + 2);
52
+ const entriesStr = entries
53
+ .map(([k, v]) => {
54
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
55
+ return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
56
+ })
57
+ .join(',\n');
58
+ return `{\n${entriesStr},\n${spaces}}`;
59
+ }
60
+
61
+ return String(value);
62
+ }
63
+
64
+ function generateTypeScriptContent(obj, langCode) {
65
+ const langName = getLangDisplayName(langCode);
66
+ const objString = stringifyValue(obj, 0);
67
+
68
+ return `/**
69
+ * ${langName} Translations
70
+ * Auto-translated from en-US.ts
71
+ */
72
+
73
+ export default ${objString};
74
+ `;
75
+ }
76
+
77
+ module.exports = {
78
+ parseTypeScriptFile,
79
+ generateTypeScriptContent,
80
+ };
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Sync Helper
5
+ * Helper functions for synchronizing translation keys
6
+ */
7
+
8
+ function addMissingKeys(enObj, targetObj, stats = { added: 0 }) {
9
+ for (const key in enObj) {
10
+ if (!Object.prototype.hasOwnProperty.call(targetObj, key)) {
11
+ targetObj[key] = enObj[key];
12
+ stats.added++;
13
+ } else if (
14
+ typeof enObj[key] === 'object' &&
15
+ enObj[key] !== null &&
16
+ !Array.isArray(enObj[key])
17
+ ) {
18
+ if (!targetObj[key] || typeof targetObj[key] !== 'object') {
19
+ targetObj[key] = {};
20
+ }
21
+ addMissingKeys(enObj[key], targetObj[key], stats);
22
+ }
23
+ }
24
+ return stats;
25
+ }
26
+
27
+ function removeExtraKeys(enObj, targetObj, stats = { removed: 0 }) {
28
+ for (const key in targetObj) {
29
+ if (!Object.prototype.hasOwnProperty.call(enObj, key)) {
30
+ delete targetObj[key];
31
+ stats.removed++;
32
+ } else if (
33
+ typeof enObj[key] === 'object' &&
34
+ enObj[key] !== null &&
35
+ !Array.isArray(enObj[key]) &&
36
+ typeof targetObj[key] === 'object' &&
37
+ targetObj[key] !== null &&
38
+ !Array.isArray(targetObj[key])
39
+ ) {
40
+ removeExtraKeys(enObj[key], targetObj[key], stats);
41
+ }
42
+ }
43
+ return stats;
44
+ }
45
+
46
+ module.exports = {
47
+ addMissingKeys,
48
+ removeExtraKeys,
49
+ };
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Translation Configuration
5
+ * Language mappings and constants for translation system
6
+ */
7
+
8
+ const LANGUAGE_MAP = {
9
+ 'ar-SA': 'ar',
10
+ 'bg-BG': 'bg',
11
+ 'cs-CZ': 'cs',
12
+ 'da-DK': 'da',
13
+ 'de-DE': 'de',
14
+ 'el-GR': 'el',
15
+ 'en-AU': 'en',
16
+ 'en-CA': 'en',
17
+ 'en-GB': 'en',
18
+ 'es-ES': 'es',
19
+ 'es-MX': 'es',
20
+ 'fi-FI': 'fi',
21
+ 'fr-CA': 'fr',
22
+ 'fr-FR': 'fr',
23
+ 'hi-IN': 'hi',
24
+ 'hr-HR': 'hr',
25
+ 'hu-HU': 'hu',
26
+ 'id-ID': 'id',
27
+ 'it-IT': 'it',
28
+ 'ja-JP': 'ja',
29
+ 'ko-KR': 'ko',
30
+ 'ms-MY': 'ms',
31
+ 'nl-NL': 'nl',
32
+ 'no-NO': 'no',
33
+ 'pl-PL': 'pl',
34
+ 'pt-BR': 'pt',
35
+ 'pt-PT': 'pt',
36
+ 'ro-RO': 'ro',
37
+ 'ru-RU': 'ru',
38
+ 'sk-SK': 'sk',
39
+ 'sv-SE': 'sv',
40
+ 'th-TH': 'th',
41
+ 'tl-PH': 'tl',
42
+ 'tr-TR': 'tr',
43
+ 'uk-UA': 'uk',
44
+ 'vi-VN': 'vi',
45
+ 'zh-CN': 'zh-CN',
46
+ 'zh-TW': 'zh-TW',
47
+ };
48
+
49
+ const SKIP_WORDS = new Set([
50
+ 'OK',
51
+ 'Email',
52
+ 'Google',
53
+ 'Apple',
54
+ 'Facebook',
55
+ 'Premium',
56
+ 'Pro',
57
+ 'Plus',
58
+ 'BPM',
59
+ ]);
60
+
61
+ const LANGUAGE_NAMES = {
62
+ 'ar-SA': 'Arabic (Saudi Arabia)',
63
+ 'bg-BG': 'Bulgarian',
64
+ 'cs-CZ': 'Czech',
65
+ 'da-DK': 'Danish',
66
+ 'de-DE': 'German',
67
+ 'el-GR': 'Greek',
68
+ 'en-AU': 'English (Australia)',
69
+ 'en-CA': 'English (Canada)',
70
+ 'en-GB': 'English (UK)',
71
+ 'en-US': 'English (US)',
72
+ 'es-ES': 'Spanish (Spain)',
73
+ 'es-MX': 'Spanish (Mexico)',
74
+ 'fi-FI': 'Finnish',
75
+ 'fr-CA': 'French (Canada)',
76
+ 'fr-FR': 'French (France)',
77
+ 'hi-IN': 'Hindi',
78
+ 'hr-HR': 'Croatian',
79
+ 'hu-HU': 'Hungarian',
80
+ 'id-ID': 'Indonesian',
81
+ 'it-IT': 'Italian',
82
+ 'ja-JP': 'Japanese',
83
+ 'ko-KR': 'Korean',
84
+ 'ms-MY': 'Malay',
85
+ 'nl-NL': 'Dutch',
86
+ 'no-NO': 'Norwegian',
87
+ 'pl-PL': 'Polish',
88
+ 'pt-BR': 'Portuguese (Brazil)',
89
+ 'pt-PT': 'Portuguese (Portugal)',
90
+ 'ro-RO': 'Romanian',
91
+ 'ru-RU': 'Russian',
92
+ 'sk-SK': 'Slovak',
93
+ 'sv-SE': 'Swedish',
94
+ 'th-TH': 'Thai',
95
+ 'tl-PH': 'Tagalog',
96
+ 'tr-TR': 'Turkish',
97
+ 'uk-UA': 'Ukrainian',
98
+ 'vi-VN': 'Vietnamese',
99
+ 'zh-CN': 'Chinese (Simplified)',
100
+ 'zh-TW': 'Chinese (Traditional)',
101
+ };
102
+
103
+ function getLangDisplayName(code) {
104
+ return LANGUAGE_NAMES[code] || code;
105
+ }
106
+
107
+ function getTargetLanguage(langCode) {
108
+ return LANGUAGE_MAP[langCode];
109
+ }
110
+
111
+ function shouldSkipWord(word) {
112
+ return SKIP_WORDS.has(word);
113
+ }
114
+
115
+ function isEnglishVariant(langCode) {
116
+ const targetLang = LANGUAGE_MAP[langCode];
117
+ return targetLang === 'en';
118
+ }
119
+
120
+ module.exports = {
121
+ LANGUAGE_MAP,
122
+ SKIP_WORDS,
123
+ LANGUAGE_NAMES,
124
+ getLangDisplayName,
125
+ getTargetLanguage,
126
+ shouldSkipWord,
127
+ isEnglishVariant,
128
+ };
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Translator
5
+ * Google Translate API integration and translation logic
6
+ */
7
+
8
+ const https = require('https');
9
+ const { shouldSkipWord } = require('./translation-config');
10
+
11
+ function delay(ms) {
12
+ return new Promise(resolve => setTimeout(resolve, ms));
13
+ }
14
+
15
+ async function translateText(text, targetLang) {
16
+ return new Promise((resolve) => {
17
+ if (shouldSkipWord(text)) {
18
+ resolve(text);
19
+ return;
20
+ }
21
+
22
+ const encodedText = encodeURIComponent(text);
23
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodedText}`;
24
+
25
+ https
26
+ .get(url, res => {
27
+ let data = '';
28
+ res.on('data', chunk => {
29
+ data += chunk;
30
+ });
31
+ res.on('end', () => {
32
+ try {
33
+ const parsed = JSON.parse(data);
34
+ const translated = parsed[0]
35
+ .map(item => item[0])
36
+ .join('')
37
+ .trim();
38
+ resolve(translated || text);
39
+ } catch (error) {
40
+ resolve(text);
41
+ }
42
+ });
43
+ })
44
+ .on('error', () => {
45
+ resolve(text);
46
+ });
47
+ });
48
+ }
49
+
50
+ function needsTranslation(value, enValue) {
51
+ if (typeof value !== 'string') return false;
52
+ if (value === enValue) return true;
53
+ if (shouldSkipWord(value)) return false;
54
+ return false;
55
+ }
56
+
57
+ async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0 }) {
58
+ for (const key in enObj) {
59
+ const currentPath = path ? `${path}.${key}` : key;
60
+ const enValue = enObj[key];
61
+ const targetValue = targetObj[key];
62
+
63
+ if (Array.isArray(enValue)) {
64
+ if (!Array.isArray(targetValue)) {
65
+ targetObj[key] = [];
66
+ }
67
+ for (let i = 0; i < enValue.length; i++) {
68
+ if (typeof enValue[i] === 'string') {
69
+ if (needsTranslation(targetObj[key][i], enValue[i])) {
70
+ targetObj[key][i] = await translateText(enValue[i], targetLang);
71
+ stats.count++;
72
+ await delay(200);
73
+ }
74
+ }
75
+ }
76
+ } else if (typeof enValue === 'object' && enValue !== null) {
77
+ if (!targetObj[key] || typeof targetObj[key] !== 'object') {
78
+ targetObj[key] = {};
79
+ }
80
+ await translateObject(enValue, targetObj[key], targetLang, currentPath, stats);
81
+ } else if (typeof enValue === 'string') {
82
+ if (needsTranslation(targetValue, enValue)) {
83
+ targetObj[key] = await translateText(enValue, targetLang);
84
+ stats.count++;
85
+ await delay(200);
86
+ }
87
+ }
88
+ }
89
+
90
+ return stats.count;
91
+ }
92
+
93
+ module.exports = {
94
+ translateText,
95
+ translateObject,
96
+ delay,
97
+ };
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env node
2
- /* eslint-disable no-console */
3
-
4
- /**
5
- * Pre-Publish Script - Generic Package Version
6
- *
7
- * Basic checks before publishing for generic localization package
8
- */
9
-
10
- import * as fs from 'fs';
11
- import * as path from 'path';
12
- import { fileURLToPath } from 'url';
13
-
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = path.dirname(__filename);
16
- const PACKAGE_ROOT = path.resolve(__dirname, '..');
17
- const SRC_DIR = path.join(PACKAGE_ROOT, 'src');
18
-
19
- console.log('🔍 Pre-publish checks...\n');
20
-
21
- // Check if src directory exists
22
- if (!fs.existsSync(SRC_DIR)) {
23
- console.error('❌ src directory not found!');
24
- process.exit(1);
25
- }
26
-
27
- // Check if main files exist
28
- const mainFiles = [
29
- 'src/index.ts',
30
- 'src/infrastructure/config/i18n.ts',
31
- 'src/infrastructure/storage/LocalizationStore.ts',
32
- ];
33
-
34
- let allFilesExist = true;
35
- for (const file of mainFiles) {
36
- const filePath = path.join(PACKAGE_ROOT, file);
37
- if (!fs.existsSync(filePath)) {
38
- console.error(`❌ Required file not found: ${file}`);
39
- allFilesExist = false;
40
- }
41
- }
42
-
43
- if (!allFilesExist) {
44
- process.exit(1);
45
- }
46
-
47
- console.log('✅ All required files found');
48
- console.log('✅ Pre-publish checks passed!\n');