Skip to content

Commit d2c86f2

Browse files
authored
Merge pull request #9023 from neznaika0/feat-lang-sync
feat: New command `lang:sync`
2 parents e475fd8 + 1eae834 commit d2c86f2

File tree

4 files changed

+504
-0
lines changed

4 files changed

+504
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) CodeIgniter Foundation <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\Commands\Translation;
15+
16+
use CodeIgniter\CLI\BaseCommand;
17+
use CodeIgniter\CLI\CLI;
18+
use CodeIgniter\Exceptions\LogicException;
19+
use Config\App;
20+
use ErrorException;
21+
use FilesystemIterator;
22+
use Locale;
23+
use RecursiveDirectoryIterator;
24+
use RecursiveIteratorIterator;
25+
use SplFileInfo;
26+
27+
/**
28+
* @see \CodeIgniter\Commands\Translation\LocalizationSyncTest
29+
*/
30+
class LocalizationSync extends BaseCommand
31+
{
32+
protected $group = 'Translation';
33+
protected $name = 'lang:sync';
34+
protected $description = 'Synchronize translation files from one language to another.';
35+
protected $usage = 'lang:sync [options]';
36+
protected $arguments = [];
37+
protected $options = [
38+
'--locale' => 'The original locale (en, ru, etc.).',
39+
'--target' => 'Target locale (en, ru, etc.).',
40+
];
41+
private string $languagePath;
42+
43+
public function run(array $params)
44+
{
45+
$optionTargetLocale = '';
46+
$optionLocale = $params['locale'] ?? Locale::getDefault();
47+
$this->languagePath = APPPATH . 'Language';
48+
49+
if (isset($params['target']) && $params['target'] !== '') {
50+
$optionTargetLocale = $params['target'];
51+
}
52+
53+
if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) {
54+
CLI::error(
55+
'Error: "' . $optionLocale . '" is not supported. Supported locales: '
56+
. implode(', ', config(App::class)->supportedLocales)
57+
);
58+
59+
return EXIT_USER_INPUT;
60+
}
61+
62+
if ($optionTargetLocale === '') {
63+
CLI::error(
64+
'Error: "--target" is not configured. Supported locales: '
65+
. implode(', ', config(App::class)->supportedLocales)
66+
);
67+
68+
return EXIT_USER_INPUT;
69+
}
70+
71+
if (! in_array($optionTargetLocale, config(App::class)->supportedLocales, true)) {
72+
CLI::error(
73+
'Error: "' . $optionTargetLocale . '" is not supported. Supported locales: '
74+
. implode(', ', config(App::class)->supportedLocales)
75+
);
76+
77+
return EXIT_USER_INPUT;
78+
}
79+
80+
if ($optionTargetLocale === $optionLocale) {
81+
CLI::error(
82+
'Error: You cannot have the same values for "--target" and "--locale".'
83+
);
84+
85+
return EXIT_USER_INPUT;
86+
}
87+
88+
if (ENVIRONMENT === 'testing') {
89+
$this->languagePath = SUPPORTPATH . 'Language';
90+
}
91+
92+
if ($this->process($optionLocale, $optionTargetLocale) === EXIT_ERROR) {
93+
return EXIT_ERROR;
94+
}
95+
96+
CLI::write('All operations done!');
97+
98+
return EXIT_SUCCESS;
99+
}
100+
101+
private function process(string $originalLocale, string $targetLocale): int
102+
{
103+
$originalLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $originalLocale;
104+
$targetLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $targetLocale;
105+
106+
if (! is_dir($originalLocaleDir)) {
107+
CLI::error(
108+
'Error: The "' . clean_path($originalLocaleDir) . '" directory was not found.'
109+
);
110+
111+
return EXIT_ERROR;
112+
}
113+
114+
// Unifying the error - mkdir() may cause an exception.
115+
try {
116+
if (! is_dir($targetLocaleDir) && ! mkdir($targetLocaleDir, 0775)) {
117+
throw new ErrorException();
118+
}
119+
} catch (ErrorException $e) {
120+
CLI::error(
121+
'Error: The target directory "' . clean_path($targetLocaleDir) . '" cannot be accessed.'
122+
);
123+
124+
return EXIT_ERROR;
125+
}
126+
127+
$iterator = new RecursiveIteratorIterator(
128+
new RecursiveDirectoryIterator(
129+
$originalLocaleDir,
130+
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS
131+
)
132+
);
133+
134+
/**
135+
* @var array<non-empty-string, SplFileInfo> $files
136+
*/
137+
$files = iterator_to_array($iterator, true);
138+
ksort($files);
139+
140+
foreach ($files as $originalLanguageFile) {
141+
if ($originalLanguageFile->getExtension() !== 'php') {
142+
continue;
143+
}
144+
145+
$targetLanguageFile = $targetLocaleDir . DIRECTORY_SEPARATOR . $originalLanguageFile->getFilename();
146+
147+
$targetLanguageKeys = [];
148+
$originalLanguageKeys = include $originalLanguageFile;
149+
150+
if (is_file($targetLanguageFile)) {
151+
$targetLanguageKeys = include $targetLanguageFile;
152+
}
153+
154+
$targetLanguageKeys = $this->mergeLanguageKeys($originalLanguageKeys, $targetLanguageKeys, $originalLanguageFile->getBasename('.php'));
155+
156+
$content = "<?php\n\nreturn " . var_export($targetLanguageKeys, true) . ";\n";
157+
file_put_contents($targetLanguageFile, $content);
158+
}
159+
160+
return EXIT_SUCCESS;
161+
}
162+
163+
/**
164+
* @param array<string, array<string,mixed>|string|null> $originalLanguageKeys
165+
* @param array<string, array<string,mixed>|string|null> $targetLanguageKeys
166+
*
167+
* @return array<string, array<string,mixed>|string|null>
168+
*/
169+
private function mergeLanguageKeys(array $originalLanguageKeys, array $targetLanguageKeys, string $prefix = ''): array
170+
{
171+
$mergedLanguageKeys = [];
172+
173+
foreach ($originalLanguageKeys as $key => $value) {
174+
$placeholderValue = $prefix !== '' ? $prefix . '.' . $key : $key;
175+
176+
if (is_string($value)) {
177+
// Keep the old value
178+
// TODO: The value type may not match the original one
179+
if (array_key_exists($key, $targetLanguageKeys)) {
180+
$mergedLanguageKeys[$key] = $targetLanguageKeys[$key];
181+
182+
continue;
183+
}
184+
185+
// Set new key with placeholder
186+
$mergedLanguageKeys[$key] = $placeholderValue;
187+
} elseif (is_array($value)) {
188+
if (! array_key_exists($key, $targetLanguageKeys)) {
189+
$mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, [], $placeholderValue);
190+
191+
continue;
192+
}
193+
194+
$mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, $targetLanguageKeys[$key], $placeholderValue);
195+
} else {
196+
throw new LogicException('Value for the key "' . $placeholderValue . '" is of the wrong type. Only "array" or "string" is allowed.');
197+
}
198+
}
199+
200+
return $mergedLanguageKeys;
201+
}
202+
}

0 commit comments

Comments
 (0)