@umituz/react-native-localization 2.0.1 ā 2.1.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.
- package/package.json +2 -12
- package/scripts/prepublish.js +7 -52
- package/scripts/analyze-keys.js +0 -230
- package/scripts/check-translations.js +0 -354
- package/scripts/createLocaleLoaders.js +0 -187
- package/scripts/remove-unused-keys.js +0 -216
- package/scripts/setup-languages.js +0 -290
- package/scripts/translate-missing.js +0 -619
- package/scripts/utils/findLocalesDir.js +0 -79
package/package.json
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-localization",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.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",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"typecheck": "tsc --noEmit --skipLibCheck",
|
|
9
9
|
"lint": "tsc --noEmit",
|
|
10
|
-
"
|
|
11
|
-
"locales:generate:lang": "node scripts/createLocaleLoaders.js",
|
|
12
|
-
"prepublishOnly": "node scripts/prepublish.js",
|
|
13
|
-
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
14
|
-
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
15
|
-
"version:major": "npm version major -m 'chore: release v%s'",
|
|
16
|
-
"i18n:setup": "node scripts/setup-languages.js",
|
|
17
|
-
"i18n:translate": "node scripts/translate-missing.js",
|
|
18
|
-
"i18n:check": "node scripts/check-translations.js",
|
|
19
|
-
"i18n:analyze": "node scripts/analyze-keys.js",
|
|
20
|
-
"i18n:remove-unused": "node scripts/remove-unused-keys.js"
|
|
10
|
+
"prepublishOnly": "node scripts/prepublish.js"
|
|
21
11
|
},
|
|
22
12
|
"keywords": [
|
|
23
13
|
"react-native",
|
package/scripts/prepublish.js
CHANGED
|
@@ -2,79 +2,34 @@
|
|
|
2
2
|
/* eslint-disable no-console */
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Pre-Publish Script
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 1. en-US translation files are ready
|
|
9
|
-
* 2. en-US index.ts loader is generated
|
|
10
|
-
* 3. All required files are in place
|
|
11
|
-
*
|
|
12
|
-
* Runs automatically via "prepublishOnly" npm script
|
|
5
|
+
* Pre-Publish Script - Minimal Version
|
|
6
|
+
*
|
|
7
|
+
* Basic checks before publishing
|
|
13
8
|
*/
|
|
14
9
|
|
|
15
10
|
const fs = require('fs');
|
|
16
11
|
const path = require('path');
|
|
17
|
-
const { execSync } = require('child_process');
|
|
18
12
|
|
|
19
13
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
20
14
|
const EN_US_DIR = path.join(PACKAGE_ROOT, 'src/infrastructure/locales/en-US');
|
|
21
|
-
const EN_US_INDEX = path.join(EN_US_DIR, 'index.ts');
|
|
22
15
|
|
|
23
16
|
console.log('š Pre-publish checks...\n');
|
|
24
17
|
|
|
25
18
|
// Check if en-US directory exists
|
|
26
19
|
if (!fs.existsSync(EN_US_DIR)) {
|
|
27
20
|
console.error('ā en-US directory not found!');
|
|
28
|
-
console.error(` Expected: ${EN_US_DIR}`);
|
|
29
21
|
process.exit(1);
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
// Check if en-US has JSON files
|
|
33
25
|
const jsonFiles = fs.readdirSync(EN_US_DIR)
|
|
34
|
-
.filter(file => file.endsWith('.json'))
|
|
35
|
-
.sort();
|
|
26
|
+
.filter(file => file.endsWith('.json'));
|
|
36
27
|
|
|
37
28
|
if (jsonFiles.length === 0) {
|
|
38
|
-
console.error('ā No JSON translation files found
|
|
29
|
+
console.error('ā No JSON translation files found!');
|
|
39
30
|
process.exit(1);
|
|
40
31
|
}
|
|
41
32
|
|
|
42
|
-
console.log(`ā
Found ${jsonFiles.length} translation files
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Generate index.ts if it doesn't exist or is outdated
|
|
46
|
-
const needsIndexUpdate = !fs.existsSync(EN_US_INDEX) ||
|
|
47
|
-
jsonFiles.some(file => {
|
|
48
|
-
const filePath = path.join(EN_US_DIR, file);
|
|
49
|
-
const indexStat = fs.statSync(EN_US_INDEX);
|
|
50
|
-
const fileStat = fs.statSync(filePath);
|
|
51
|
-
return fileStat.mtime > indexStat.mtime;
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (needsIndexUpdate) {
|
|
55
|
-
console.log('\nš Generating en-US index.ts loader...');
|
|
56
|
-
try {
|
|
57
|
-
// Run createLocaleLoaders script for en-US
|
|
58
|
-
const createLoaderScript = path.join(PACKAGE_ROOT, 'scripts/createLocaleLoaders.js');
|
|
59
|
-
execSync(`node "${createLoaderScript}" en-US`, {
|
|
60
|
-
stdio: 'inherit',
|
|
61
|
-
cwd: PACKAGE_ROOT,
|
|
62
|
-
});
|
|
63
|
-
console.log('ā
en-US index.ts generated successfully');
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('ā Failed to generate en-US index.ts:', error.message);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
console.log('\nā
en-US index.ts is up to date');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Verify index.ts exists
|
|
73
|
-
if (!fs.existsSync(EN_US_INDEX)) {
|
|
74
|
-
console.error('ā en-US/index.ts not found after generation!');
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log('\nā
Pre-publish checks passed!');
|
|
79
|
-
console.log(' Package is ready to publish.\n');
|
|
33
|
+
console.log(`ā
Found ${jsonFiles.length} translation files`);
|
|
34
|
+
console.log('ā
Pre-publish checks passed!\n');
|
|
80
35
|
|
package/scripts/analyze-keys.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Translation Key Analyzer
|
|
6
|
-
*
|
|
7
|
-
* Analyzes React Native apps to find all translation keys used
|
|
8
|
-
* in screens and compares them against available translations.
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* npm run i18n:analyze
|
|
12
|
-
* node node_modules/@umituz/react-native-localization/scripts/analyze-keys.js
|
|
13
|
-
*
|
|
14
|
-
* Features:
|
|
15
|
-
* - Extracts all t('...') calls from .tsx/.ts files
|
|
16
|
-
* - Validates against en-US translation files
|
|
17
|
-
* - Reports missing keys
|
|
18
|
-
* - Reports unused keys (in JSON but not in code)
|
|
19
|
-
* - Generates comprehensive report
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
const fs = require('fs');
|
|
23
|
-
const path = require('path');
|
|
24
|
-
const { getLocalesDir } = require('./utils/findLocalesDir');
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Extract translation keys from a TypeScript/TSX file
|
|
28
|
-
* Looks for patterns: t('key'), t("key"), t(`key`)
|
|
29
|
-
*/
|
|
30
|
-
function extractKeysFromFile(filePath) {
|
|
31
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
32
|
-
const keys = new Set();
|
|
33
|
-
|
|
34
|
-
// Regex patterns for t('key'), t("key"), t(`key`)
|
|
35
|
-
const patterns = [
|
|
36
|
-
/t\(['"]([^'"]+)['"]\)/g, // t('key') or t("key")
|
|
37
|
-
/t\(`([^`]+)`\)/g, // t(`key`)
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
for (const pattern of patterns) {
|
|
41
|
-
let match;
|
|
42
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
43
|
-
const key = match[1].trim();
|
|
44
|
-
|
|
45
|
-
// Skip invalid keys
|
|
46
|
-
if (
|
|
47
|
-
!key || // Empty
|
|
48
|
-
key.length < 3 || // Too short (single chars like '/', '-')
|
|
49
|
-
key.includes('${') || // Variables ${var}
|
|
50
|
-
key.includes('{{') || // Handlebars {{var}}
|
|
51
|
-
/^[A-Z][a-z]+$/.test(key) || // Single words like "Main", "Settings" (likely screen names)
|
|
52
|
-
/^[a-z]+$/.test(key) || // Single words like "button", "icon" (likely prop values)
|
|
53
|
-
/^[a-z]+-[a-z]+$/.test(key) // Kebab-case like "onboarding-complete" (likely testIDs)
|
|
54
|
-
) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
keys.add(key);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return keys;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Recursively find all TypeScript/TSX files in a directory
|
|
67
|
-
*/
|
|
68
|
-
function findSourceFiles(dir, files = []) {
|
|
69
|
-
if (!fs.existsSync(dir)) {
|
|
70
|
-
return files;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
74
|
-
|
|
75
|
-
for (const entry of entries) {
|
|
76
|
-
const fullPath = path.join(dir, entry.name);
|
|
77
|
-
|
|
78
|
-
// Skip node_modules, .git, build directories
|
|
79
|
-
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'build' || entry.name === 'lib') {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (entry.isDirectory()) {
|
|
84
|
-
findSourceFiles(fullPath, files);
|
|
85
|
-
} else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
|
|
86
|
-
files.push(fullPath);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return files;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Load all translation keys from JSON files
|
|
95
|
-
*/
|
|
96
|
-
function loadTranslationKeys(localesDir, locale = 'en-US') {
|
|
97
|
-
const localeDir = path.join(localesDir, locale);
|
|
98
|
-
const keys = new Set();
|
|
99
|
-
|
|
100
|
-
if (!fs.existsSync(localeDir)) {
|
|
101
|
-
return keys;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.json'));
|
|
105
|
-
|
|
106
|
-
for (const file of files) {
|
|
107
|
-
const filePath = path.join(localeDir, file);
|
|
108
|
-
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
109
|
-
const namespace = file.replace('.json', '');
|
|
110
|
-
|
|
111
|
-
extractKeysFromObject(content, namespace, keys);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return keys;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Recursively extract keys from translation object
|
|
119
|
-
*/
|
|
120
|
-
function extractKeysFromObject(obj, prefix, keys) {
|
|
121
|
-
for (const key in obj) {
|
|
122
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
123
|
-
|
|
124
|
-
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
125
|
-
extractKeysFromObject(obj[key], fullKey, keys);
|
|
126
|
-
} else if (typeof obj[key] === 'string') {
|
|
127
|
-
keys.add(fullKey);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Analyze translation keys for current project
|
|
134
|
-
*/
|
|
135
|
-
function analyzeProject() {
|
|
136
|
-
console.log(`\nš Analyzing translation keys for current project...\n`);
|
|
137
|
-
|
|
138
|
-
// Find project's locales directory
|
|
139
|
-
const localesDir = getLocalesDir();
|
|
140
|
-
const projectRoot = process.cwd();
|
|
141
|
-
|
|
142
|
-
console.log(`š Project root: ${projectRoot}`);
|
|
143
|
-
console.log(`š Locales directory: ${localesDir}\n`);
|
|
144
|
-
|
|
145
|
-
// Find all source files in src directory
|
|
146
|
-
const srcDir = path.join(projectRoot, 'src');
|
|
147
|
-
const sourceFiles = findSourceFiles(srcDir);
|
|
148
|
-
console.log(`š Found ${sourceFiles.length} source files\n`);
|
|
149
|
-
|
|
150
|
-
// Extract keys from source code
|
|
151
|
-
const usedKeys = new Set();
|
|
152
|
-
for (const file of sourceFiles) {
|
|
153
|
-
const keys = extractKeysFromFile(file);
|
|
154
|
-
keys.forEach(key => usedKeys.add(key));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
console.log(`š Found ${usedKeys.size} translation keys in source code\n`);
|
|
158
|
-
|
|
159
|
-
// Load translation keys
|
|
160
|
-
const translationKeys = loadTranslationKeys(localesDir);
|
|
161
|
-
console.log(`š Found ${translationKeys.size} translation keys in en-US JSON files\n`);
|
|
162
|
-
|
|
163
|
-
// Find missing keys (used in code but not in translations)
|
|
164
|
-
const missingKeys = [];
|
|
165
|
-
usedKeys.forEach(key => {
|
|
166
|
-
if (!translationKeys.has(key)) {
|
|
167
|
-
missingKeys.push(key);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Find unused keys (in translations but not used in code)
|
|
172
|
-
const unusedKeys = [];
|
|
173
|
-
translationKeys.forEach(key => {
|
|
174
|
-
if (!usedKeys.has(key)) {
|
|
175
|
-
unusedKeys.push(key);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Generate report
|
|
180
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
181
|
-
console.log('š TRANSLATION KEY ANALYSIS REPORT\n');
|
|
182
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
183
|
-
|
|
184
|
-
if (missingKeys.length === 0) {
|
|
185
|
-
console.log('ā
All translation keys found in JSON files!\n');
|
|
186
|
-
} else {
|
|
187
|
-
console.log(`ā Missing Keys: ${missingKeys.length}\n`);
|
|
188
|
-
console.log('Keys used in code but NOT found in translation files:\n');
|
|
189
|
-
missingKeys.sort().forEach(key => {
|
|
190
|
-
console.log(` - ${key}`);
|
|
191
|
-
});
|
|
192
|
-
console.log('');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (unusedKeys.length > 0) {
|
|
196
|
-
console.log(`ā ļø Unused Keys: ${unusedKeys.length}\n`);
|
|
197
|
-
console.log('Keys in translation files but NOT used in code:\n');
|
|
198
|
-
unusedKeys.sort().forEach(key => {
|
|
199
|
-
console.log(` - ${key}`);
|
|
200
|
-
});
|
|
201
|
-
console.log('');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
205
|
-
console.log('š Summary:\n');
|
|
206
|
-
console.log(` Total keys in code: ${usedKeys.size}`);
|
|
207
|
-
console.log(` Total keys in translations: ${translationKeys.size}`);
|
|
208
|
-
console.log(` Missing keys: ${missingKeys.length}`);
|
|
209
|
-
console.log(` Unused keys: ${unusedKeys.length}`);
|
|
210
|
-
if (usedKeys.size > 0) {
|
|
211
|
-
console.log(` Coverage: ${Math.round((1 - missingKeys.length / usedKeys.size) * 100)}%`);
|
|
212
|
-
}
|
|
213
|
-
console.log('');
|
|
214
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
215
|
-
|
|
216
|
-
if (missingKeys.length > 0) {
|
|
217
|
-
console.log('š” Next Steps:\n');
|
|
218
|
-
console.log(' 1. Add missing keys to en-US JSON files');
|
|
219
|
-
console.log(' 2. Run: npm run i18n:translate to auto-translate');
|
|
220
|
-
console.log(' 3. Re-run this analyzer to verify\n');
|
|
221
|
-
process.exit(1);
|
|
222
|
-
} else {
|
|
223
|
-
console.log('ā
Translation check passed!\n');
|
|
224
|
-
process.exit(0);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Main
|
|
229
|
-
analyzeProject();
|
|
230
|
-
|
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { getLocalesDir } = require('./utils/findLocalesDir');
|
|
7
|
-
|
|
8
|
-
// Function to get all keys from an object recursively
|
|
9
|
-
function getAllKeys(obj, prefix = '') {
|
|
10
|
-
let keys = [];
|
|
11
|
-
for (const key in obj) {
|
|
12
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
13
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
14
|
-
if (
|
|
15
|
-
typeof obj[key] === 'object' &&
|
|
16
|
-
obj[key] !== null &&
|
|
17
|
-
!Array.isArray(obj[key])
|
|
18
|
-
) {
|
|
19
|
-
keys = keys.concat(getAllKeys(obj[key], fullKey));
|
|
20
|
-
} else {
|
|
21
|
-
keys.push(fullKey);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return keys;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Function to get value by key path
|
|
29
|
-
function getValueByPath(obj, path) {
|
|
30
|
-
return path.split('.').reduce((current, key) => current && current[key], obj);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Function to compare two translation objects
|
|
34
|
-
function compareTranslations(enUS, targetLang, filePath, langCode) {
|
|
35
|
-
const enKeys = getAllKeys(enUS);
|
|
36
|
-
const targetKeys = getAllKeys(targetLang);
|
|
37
|
-
|
|
38
|
-
const missingInTarget = enKeys.filter(key => !targetKeys.includes(key));
|
|
39
|
-
const extraInTarget = targetKeys.filter(key => !enKeys.includes(key));
|
|
40
|
-
|
|
41
|
-
const issues = [];
|
|
42
|
-
|
|
43
|
-
// Check for missing keys in target language
|
|
44
|
-
if (missingInTarget.length > 0) {
|
|
45
|
-
issues.push({
|
|
46
|
-
type: 'missing',
|
|
47
|
-
message: `Missing keys in ${langCode}: ${missingInTarget.join(', ')}`,
|
|
48
|
-
keys: missingInTarget,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Check for extra keys in target language
|
|
53
|
-
if (extraInTarget.length > 0) {
|
|
54
|
-
issues.push({
|
|
55
|
-
type: 'extra',
|
|
56
|
-
message: `Extra keys in ${langCode}: ${extraInTarget.join(', ')}`,
|
|
57
|
-
keys: extraInTarget,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check for empty or placeholder values in target language
|
|
62
|
-
enKeys.forEach(key => {
|
|
63
|
-
if (targetKeys.includes(key)) {
|
|
64
|
-
const enValue = getValueByPath(enUS, key);
|
|
65
|
-
const targetValue = getValueByPath(targetLang, key);
|
|
66
|
-
|
|
67
|
-
// Skip checking if the value is the same and it's a common English word that doesn't need translation
|
|
68
|
-
const commonEnglishWords = [
|
|
69
|
-
'Premium',
|
|
70
|
-
'EULA',
|
|
71
|
-
'Plan',
|
|
72
|
-
'OK',
|
|
73
|
-
'API',
|
|
74
|
-
'URL',
|
|
75
|
-
'iOS',
|
|
76
|
-
'Android',
|
|
77
|
-
'minute',
|
|
78
|
-
'min',
|
|
79
|
-
'Sessions',
|
|
80
|
-
'Points',
|
|
81
|
-
'Nature',
|
|
82
|
-
'Instrumental',
|
|
83
|
-
'Piano',
|
|
84
|
-
'Silence',
|
|
85
|
-
'Pause',
|
|
86
|
-
'Gratitude',
|
|
87
|
-
'Notes',
|
|
88
|
-
'5 min',
|
|
89
|
-
'10 min',
|
|
90
|
-
'15 min',
|
|
91
|
-
'20 min',
|
|
92
|
-
'30 min',
|
|
93
|
-
'{{count}} min',
|
|
94
|
-
'{{duration}} min',
|
|
95
|
-
'{{duration}} ⢠{{cycles}} cycles',
|
|
96
|
-
'No',
|
|
97
|
-
'Error',
|
|
98
|
-
'Try Again',
|
|
99
|
-
'Back',
|
|
100
|
-
'Oops! Something went wrong',
|
|
101
|
-
"We encountered an unexpected error. Don't worry, your meditation data is safe.",
|
|
102
|
-
"This screen doesn't exist.",
|
|
103
|
-
'Go to home screen!',
|
|
104
|
-
'No meditation cards available',
|
|
105
|
-
"Thank you for reporting this bug! We'll investigate and fix it as soon as possible.",
|
|
106
|
-
"Thank you for your feature request! We'll consider it for future updates.",
|
|
107
|
-
'Thank you for your suggestion! We appreciate your input and will review it carefully.',
|
|
108
|
-
'Your feedback has been submitted successfully. We appreciate your input!',
|
|
109
|
-
'Continue',
|
|
110
|
-
'Go Home',
|
|
111
|
-
'Start Meditating',
|
|
112
|
-
'Begin Your Journey',
|
|
113
|
-
'Detailed statistics',
|
|
114
|
-
'Achievement badges',
|
|
115
|
-
'Weekly/monthly reports',
|
|
116
|
-
'Sign Up',
|
|
117
|
-
'Sign In',
|
|
118
|
-
'Breathe In',
|
|
119
|
-
'Hold',
|
|
120
|
-
'Breathe Out',
|
|
121
|
-
'Rest',
|
|
122
|
-
'Find a comfortable seated position',
|
|
123
|
-
'Focus on slow, controlled movements',
|
|
124
|
-
"Don't force the breath, let it flow naturally",
|
|
125
|
-
'Cycle {{current}} of {{total}}',
|
|
126
|
-
'{{inhale}}s in ⢠{{hold}}s hold ⢠{{exhale}}s out',
|
|
127
|
-
'Total elapsed: {{time}}',
|
|
128
|
-
'Cancel',
|
|
129
|
-
'End Session',
|
|
130
|
-
'Reset',
|
|
131
|
-
"Today's Progress",
|
|
132
|
-
'See All',
|
|
133
|
-
'Total Time',
|
|
134
|
-
'Streak',
|
|
135
|
-
'Last Session',
|
|
136
|
-
'minutes completed',
|
|
137
|
-
'Find a quiet, comfortable space',
|
|
138
|
-
'Focus on your breath naturally',
|
|
139
|
-
'Let thoughts come and go without judgment',
|
|
140
|
-
'Start with shorter sessions and build up',
|
|
141
|
-
'Legal',
|
|
142
|
-
'Loading sounds...',
|
|
143
|
-
];
|
|
144
|
-
const shouldSkip =
|
|
145
|
-
commonEnglishWords.includes(enValue) && targetValue === enValue;
|
|
146
|
-
|
|
147
|
-
// Check if value needs translation
|
|
148
|
-
// Skip if already translated (different from English) - protects manual translations
|
|
149
|
-
const isAlreadyTranslated = targetValue !== enValue;
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
!shouldSkip &&
|
|
153
|
-
!isAlreadyTranslated &&
|
|
154
|
-
(!targetValue ||
|
|
155
|
-
(typeof targetValue === 'string' && targetValue.trim() === '') ||
|
|
156
|
-
targetValue === enValue ||
|
|
157
|
-
targetValue.includes('[NEEDS TRANSLATION]') ||
|
|
158
|
-
targetValue.includes('[MISSING:') ||
|
|
159
|
-
targetValue.includes('[ĆEVİRİ GEREKLİ:') ||
|
|
160
|
-
targetValue.includes('[TRANSLATE:'))
|
|
161
|
-
) {
|
|
162
|
-
issues.push({
|
|
163
|
-
type: 'translation',
|
|
164
|
-
message: `Key "${key}" has empty, missing, or untranslated value`,
|
|
165
|
-
key: key,
|
|
166
|
-
enValue: enValue,
|
|
167
|
-
targetValue: targetValue,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
file: filePath,
|
|
175
|
-
issues: issues,
|
|
176
|
-
totalEnKeys: enKeys.length,
|
|
177
|
-
totalTargetKeys: targetKeys.length,
|
|
178
|
-
missingCount: missingInTarget.length,
|
|
179
|
-
extraCount: extraInTarget.length,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Main function
|
|
184
|
-
function main() {
|
|
185
|
-
const args = process.argv.slice(2);
|
|
186
|
-
const targetLanguage = args.find(arg => !arg.startsWith('--'));
|
|
187
|
-
|
|
188
|
-
if (!targetLanguage) {
|
|
189
|
-
console.log('Kullanım: npm run i18n:check <dil-kodu>');
|
|
190
|
-
console.log('Ćrnek: npm run i18n:check de-DE');
|
|
191
|
-
console.log('Ćrnek: npm run i18n:check fr-FR');
|
|
192
|
-
console.log('Ćrnek: npm run i18n:check tr-TR');
|
|
193
|
-
console.log(
|
|
194
|
-
'Ćrnek: npm run i18n:check spanish (tüm İspanyolca varyantları)'
|
|
195
|
-
);
|
|
196
|
-
console.log(
|
|
197
|
-
'\nTüm dilleri kontrol etmek için: npm run i18n:check all'
|
|
198
|
-
);
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Find project's locales directory
|
|
203
|
-
const localesDir = getLocalesDir();
|
|
204
|
-
const enUSDir = path.join(localesDir, 'en-US');
|
|
205
|
-
|
|
206
|
-
// š„ CRITICAL FIX: Auto-discover all JSON files (NO HARDCODED LIST!)
|
|
207
|
-
// This ensures check-translations works with ANY en-US file structure
|
|
208
|
-
const files = fs
|
|
209
|
-
.readdirSync(enUSDir)
|
|
210
|
-
.filter(file => file.endsWith('.json'))
|
|
211
|
-
.sort();
|
|
212
|
-
|
|
213
|
-
if (files.length === 0) {
|
|
214
|
-
console.error('ā No JSON files found in en-US directory!');
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
console.log(`š Discovered ${files.length} translation files in en-US:\n ${files.join(', ')}\n`);
|
|
219
|
-
|
|
220
|
-
// Get all language directories
|
|
221
|
-
const allLanguageDirs = fs
|
|
222
|
-
.readdirSync(localesDir)
|
|
223
|
-
.filter(dir => {
|
|
224
|
-
const fullPath = path.join(localesDir, dir);
|
|
225
|
-
return fs.statSync(fullPath).isDirectory() && dir !== 'en-US';
|
|
226
|
-
})
|
|
227
|
-
.sort();
|
|
228
|
-
|
|
229
|
-
// Determine which languages to process
|
|
230
|
-
let languageDirs = allLanguageDirs;
|
|
231
|
-
if (targetLanguage !== 'all') {
|
|
232
|
-
if (targetLanguage === 'spanish' || targetLanguage === 'es') {
|
|
233
|
-
// Check all Spanish variants
|
|
234
|
-
languageDirs = allLanguageDirs.filter(dir => dir.startsWith('es-'));
|
|
235
|
-
if (languageDirs.length === 0) {
|
|
236
|
-
console.log('ā No Spanish variants found.');
|
|
237
|
-
process.exit(1);
|
|
238
|
-
}
|
|
239
|
-
} else if (allLanguageDirs.includes(targetLanguage)) {
|
|
240
|
-
languageDirs = [targetLanguage];
|
|
241
|
-
} else {
|
|
242
|
-
console.log(
|
|
243
|
-
`ā Language ${targetLanguage} not found. Available languages: ${allLanguageDirs.join(', ')}`
|
|
244
|
-
);
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
let totalIssues = 0;
|
|
250
|
-
let totalMissing = 0;
|
|
251
|
-
let totalExtra = 0;
|
|
252
|
-
let totalTranslationIssues = 0;
|
|
253
|
-
let languagesWithIssues = 0;
|
|
254
|
-
|
|
255
|
-
console.log(
|
|
256
|
-
`š Checking ${languageDirs.length} language(s) against English (en-US)...\n`
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
// Check each language
|
|
260
|
-
languageDirs.forEach(langCode => {
|
|
261
|
-
const langDir = path.join(localesDir, langCode);
|
|
262
|
-
let langIssues = 0;
|
|
263
|
-
let langMissing = 0;
|
|
264
|
-
let langExtra = 0;
|
|
265
|
-
let langTranslationIssues = 0;
|
|
266
|
-
|
|
267
|
-
console.log(`\nš Checking ${langCode}...`);
|
|
268
|
-
|
|
269
|
-
files.forEach(file => {
|
|
270
|
-
const enUSPath = path.join(enUSDir, file);
|
|
271
|
-
const langPath = path.join(langDir, file);
|
|
272
|
-
|
|
273
|
-
if (!fs.existsSync(enUSPath)) {
|
|
274
|
-
console.log(` ā English file not found: ${file}`);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (!fs.existsSync(langPath)) {
|
|
279
|
-
console.log(` ā ${langCode} file not found: ${file}`);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
const enUS = JSON.parse(fs.readFileSync(enUSPath, 'utf8'));
|
|
285
|
-
const langData = JSON.parse(fs.readFileSync(langPath, 'utf8'));
|
|
286
|
-
|
|
287
|
-
const comparison = compareTranslations(enUS, langData, file, langCode);
|
|
288
|
-
|
|
289
|
-
if (comparison.issues.length === 0) {
|
|
290
|
-
console.log(` ā
${file}: Complete`);
|
|
291
|
-
} else {
|
|
292
|
-
console.log(
|
|
293
|
-
` š ${file}: ${comparison.missingCount} missing, ${comparison.extraCount} extra, ${comparison.issues.filter(i => i.type === 'translation').length} untranslated`
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
// Show detailed issues for debugging
|
|
297
|
-
comparison.issues.forEach(issue => {
|
|
298
|
-
if (issue.type === 'translation') {
|
|
299
|
-
console.log(
|
|
300
|
-
` š ${issue.key}: "${issue.enValue}" ā "${issue.targetValue || '[EMPTY]'}"`
|
|
301
|
-
);
|
|
302
|
-
} else if (issue.type === 'extra') {
|
|
303
|
-
console.log(` ā Extra keys: ${issue.keys.join(', ')}`);
|
|
304
|
-
} else if (issue.type === 'missing') {
|
|
305
|
-
console.log(` ā Missing keys: ${issue.keys.join(', ')}`);
|
|
306
|
-
}
|
|
307
|
-
langIssues++;
|
|
308
|
-
totalIssues++;
|
|
309
|
-
if (issue.type === 'missing') {
|
|
310
|
-
langMissing += issue.keys.length;
|
|
311
|
-
totalMissing += issue.keys.length;
|
|
312
|
-
} else if (issue.type === 'extra') {
|
|
313
|
-
langExtra += issue.keys.length;
|
|
314
|
-
totalExtra += issue.keys.length;
|
|
315
|
-
} else if (issue.type === 'translation') {
|
|
316
|
-
langTranslationIssues++;
|
|
317
|
-
totalTranslationIssues++;
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
} catch (error) {
|
|
322
|
-
console.log(` ā Error processing ${file}: ${error.message}`);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
// Summary for this language
|
|
327
|
-
if (langIssues === 0) {
|
|
328
|
-
console.log(` š ${langCode}: All translations complete!`);
|
|
329
|
-
} else {
|
|
330
|
-
languagesWithIssues++;
|
|
331
|
-
console.log(
|
|
332
|
-
` ā ļø ${langCode}: ${langIssues} issues (${langMissing} missing, ${langExtra} extra, ${langTranslationIssues} translation issues)`
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
console.log('\nš Summary:');
|
|
338
|
-
console.log(` Languages checked: ${languageDirs.length}`);
|
|
339
|
-
console.log(` Languages with issues: ${languagesWithIssues}`);
|
|
340
|
-
console.log(` Total issues found: ${totalIssues}`);
|
|
341
|
-
console.log(` Missing keys: ${totalMissing}`);
|
|
342
|
-
console.log(` Extra keys: ${totalExtra}`);
|
|
343
|
-
console.log(` Translation issues: ${totalTranslationIssues}`);
|
|
344
|
-
|
|
345
|
-
if (totalIssues === 0) {
|
|
346
|
-
console.log('\nš All translations are complete and correct!');
|
|
347
|
-
process.exit(0);
|
|
348
|
-
} else {
|
|
349
|
-
console.log(`\nā ļø ${languagesWithIssues} language(s) need attention.`);
|
|
350
|
-
process.exit(1);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
main();
|