Skip to content

Commit dd409ab

Browse files
authored
fix: skip already-translated keys in spark lang:find (#10308)
1 parent bce872b commit dd409ab

4 files changed

Lines changed: 105 additions & 2 deletions

File tree

system/Commands/Translation/LocalizationFinder.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,17 @@ private function process(string $currentDir, string $currentLocale): void
133133
$languageStoredKeys = require $languageFilePath;
134134
}
135135

136-
$languageDiff = ArrayHelper::recursiveDiff($foundLanguageKeys[$langFileName], $languageStoredKeys);
136+
// Keys already resolvable from any namespace's language file (framework, packages, or app)
137+
// are not new and must not be re-reported or written.
138+
$resolvedKeys = $this->findResolvedTranslations($langFileName, $currentLocale);
139+
140+
$languageDiff = ArrayHelper::recursiveDiff($foundLanguageKeys[$langFileName], $resolvedKeys);
137141
$countNewKeys += ArrayHelper::recursiveCount($languageDiff);
138142

139143
if ($this->showNew) {
140144
$tableRows = array_merge($this->arrayToTableRows($langFileName, $languageDiff), $tableRows);
141145
} else {
142-
$newLanguageKeys = array_replace_recursive($foundLanguageKeys[$langFileName], $languageStoredKeys);
146+
$newLanguageKeys = array_replace_recursive($languageDiff, $languageStoredKeys);
143147

144148
if ($languageDiff !== []) {
145149
if (file_put_contents($languageFilePath, $this->templateFile($newLanguageKeys)) === false) {
@@ -177,6 +181,35 @@ private function process(string $currentDir, string $currentLocale): void
177181
}
178182
}
179183

184+
/**
185+
* Loads the translations already resolvable for the given file and locale
186+
* from every registered namespace (framework, packages, and app).
187+
*
188+
* @return array<string, mixed>
189+
*/
190+
private function findResolvedTranslations(string $langFileName, string $currentLocale): array
191+
{
192+
$translations = [];
193+
194+
foreach (service('locator')->search("Language/{$currentLocale}/{$langFileName}.php", 'php', false) as $file) {
195+
if (! is_file($file)) {
196+
continue;
197+
}
198+
199+
$keys = require $file;
200+
201+
if (is_array($keys)) {
202+
$translations[] = $keys;
203+
}
204+
}
205+
206+
if ($translations === []) {
207+
return [];
208+
}
209+
210+
return array_replace_recursive(...$translations);
211+
}
212+
180213
/**
181214
* @param SplFileInfo|string $file
182215
*

tests/system/Commands/Translation/LocalizationFinderTest.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ final class LocalizationFinderTest extends CIUnitTestCase
3030
private static string $locale;
3131
private static string $languageTestPath;
3232
private string $originalLocale;
33+
private ?string $tempSourceDir = null;
3334

3435
/**
3536
* @var list<string>
@@ -57,6 +58,7 @@ protected function tearDown(): void
5758
parent::tearDown();
5859

5960
$this->clearGeneratedFiles();
61+
$this->clearSourceFixture();
6062
Locale::setDefault($this->originalLocale);
6163
config(App::class)->supportedLocales = $this->originalSupportedLocales;
6264
}
@@ -131,6 +133,39 @@ public function testShowBadTranslation(): void
131133
$this->assertStringContainsString($this->getActualTableWithBadKeys(), $this->getStreamFilterBuffer());
132134
}
133135

136+
public function testShowNewSkipsKeysAlreadyTranslatedByFramework(): void
137+
{
138+
$this->makeLocaleDirectory();
139+
$this->makeSourceFixture(
140+
'ResolvedKeys',
141+
"<?php\nlang('Errors.pageNotFound');\nlang('Errors.thisKeyIsBrandNew');\n",
142+
);
143+
144+
command('lang:find --dir ResolvedKeys --show-new');
145+
146+
$buffer = $this->getStreamFilterBuffer();
147+
$this->assertStringNotContainsString('Errors.pageNotFound', $buffer);
148+
$this->assertStringContainsString('Errors.thisKeyIsBrandNew', $buffer);
149+
}
150+
151+
public function testWriteSkipsKeysAlreadyTranslatedByFramework(): void
152+
{
153+
$this->makeLocaleDirectory();
154+
$this->makeSourceFixture(
155+
'ResolvedKeys',
156+
"<?php\nlang('Errors.pageNotFound');\nlang('Errors.thisKeyIsBrandNew');\n",
157+
);
158+
159+
command('lang:find --dir ResolvedKeys');
160+
161+
$generatedFile = self::$languageTestPath . self::$locale . '/Errors.php';
162+
$this->assertFileExists($generatedFile);
163+
164+
$generatedKeys = require $generatedFile;
165+
$this->assertArrayHasKey('thisKeyIsBrandNew', $generatedKeys);
166+
$this->assertArrayNotHasKey('pageNotFound', $generatedKeys);
167+
}
168+
134169
private function getActualTranslationOneKeys(): array
135170
{
136171
return [
@@ -261,6 +296,32 @@ private function makeLocaleDirectory(): void
261296
@mkdir(self::$languageTestPath . self::$locale, 0777, true);
262297
}
263298

299+
private function makeSourceFixture(string $dirName, string $contents): void
300+
{
301+
$this->tempSourceDir = SUPPORTPATH . 'Services' . DIRECTORY_SEPARATOR . $dirName;
302+
@mkdir($this->tempSourceDir, 0777, true);
303+
file_put_contents($this->tempSourceDir . DIRECTORY_SEPARATOR . 'Source.php', $contents);
304+
}
305+
306+
private function clearSourceFixture(): void
307+
{
308+
if ($this->tempSourceDir === null) {
309+
return;
310+
}
311+
312+
$sourceFile = $this->tempSourceDir . DIRECTORY_SEPARATOR . 'Source.php';
313+
314+
if (is_file($sourceFile)) {
315+
unlink($sourceFile);
316+
}
317+
318+
if (is_dir($this->tempSourceDir)) {
319+
rmdir($this->tempSourceDir);
320+
}
321+
322+
$this->tempSourceDir = null;
323+
}
324+
264325
private function clearGeneratedFiles(): void
265326
{
266327
if (is_file(self::$languageTestPath . self::$locale . '/TranslationOne.php')) {
@@ -275,6 +336,10 @@ private function clearGeneratedFiles(): void
275336
unlink(self::$languageTestPath . self::$locale . '/Translation-Four.php');
276337
}
277338

339+
if (is_file(self::$languageTestPath . self::$locale . '/Errors.php')) {
340+
unlink(self::$languageTestPath . self::$locale . '/Errors.php');
341+
}
342+
278343
if (is_dir(self::$languageTestPath . '/test_locale_incorrect')) {
279344
rmdir(self::$languageTestPath . '/test_locale_incorrect');
280345
}

user_guide_src/source/changelogs/v4.7.4.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Bugs Fixed
3131
**********
3232

3333
- **API:** Fixed a bug in Transformers where the root request's ``fields`` and ``include`` query parameters leaked into nested transformers created inside ``include*()`` methods, causing incorrect field filtering, unexpected includes, or infinite recursion.
34+
- **Commands:** Fixed a bug where ``spark lang:find`` treated translation keys already provided by the framework or another namespace (such as ``Errors.*`` in ``system/Language``) as new, listing them under ``--show-new`` and writing untranslated placeholders into ``app/Language`` that overrode the existing translations.
3435
- **Database:** Fixed a bug where ``updateBatch()`` could be called after Query Builder ``where()`` conditions, even though it's not supported. In this situation, now the ``DatabaseException`` is thrown.
3536
- **HTTP:** Fixed a bug where the User Agent library reported Safari's WebKit version instead of the browser version from the ``Version`` token.
3637
- **Model:** Fixed a bug in ``Model::objectToRawArray()`` where the ``$recursive`` parameter was ignored.

user_guide_src/source/outgoing/localization.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ After the operation, you need to translate the language keys yourself.
317317
The command is able to recognize nested keys normally ``File.array.nested.text``.
318318
Previously saved keys do not change.
319319

320+
.. note:: Keys that ``lang()`` can already resolve (for example ``Errors.*``
321+
from **system/Language**) are treated as already translated, so they are
322+
neither listed by ``--show-new`` nor written into **app/Language**.
323+
320324
.. code-block:: console
321325
322326
php spark lang:find

0 commit comments

Comments
 (0)