diff --git a/system/Language/Language.php b/system/Language/Language.php index 438f33d92286..8f6fe9aa1716 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -1,4 +1,5 @@ -language[$locale][$file][$parsedLine] ?? null; } + // if still not found, try English + if (empty($output)) + { + $this->parseLine($line, 'en'); + $output = $this->language['en'][$file][$parsedLine] ?? null; + } + $output = $output ?? $line; if (! empty($args)) @@ -278,7 +292,7 @@ protected function load(string $file, string $locale, bool $return = false) $this->loadedFiles[$locale][] = $file; // Merge our string - $this->language[$this->locale][$file] = $lang; + $this->language[$locale][$file] = $lang; } //-------------------------------------------------------------------- diff --git a/tests/_support/Language/ab-CD/Allin.php b/tests/_support/Language/ab-CD/Allin.php new file mode 100644 index 000000000000..3b153883bc35 --- /dev/null +++ b/tests/_support/Language/ab-CD/Allin.php @@ -0,0 +1,8 @@ + 'Pyramid of Giza', + 'tre' => 'Colossus of Rhodes', + 'fiv' => 'Temple of Artemis', + 'sev' => 'Hanging Gardens of Babylon', +]; diff --git a/tests/_support/Language/ab/Allin.php b/tests/_support/Language/ab/Allin.php new file mode 100644 index 000000000000..69120758f046 --- /dev/null +++ b/tests/_support/Language/ab/Allin.php @@ -0,0 +1,8 @@ + 'gluttony', + 'tre' => 'greed', + 'six' => 'envy', + 'sev' => 'pride', +]; diff --git a/tests/_support/Language/en-ZZ/More.php b/tests/_support/Language/en-ZZ/More.php new file mode 100644 index 000000000000..66810205a329 --- /dev/null +++ b/tests/_support/Language/en-ZZ/More.php @@ -0,0 +1,6 @@ + 'These are not the droids you are looking for', + 'notaMoon' => "It's made of cheese", + 'wisdom' => 'There is no try', +]; diff --git a/tests/_support/Language/en/Allin.php b/tests/_support/Language/en/Allin.php new file mode 100644 index 000000000000..6a10dcc76c1a --- /dev/null +++ b/tests/_support/Language/en/Allin.php @@ -0,0 +1,8 @@ + 'four calling birds', + 'fiv' => 'five golden rings', + 'six' => 'six geese a laying', + 'sev' => 'seven swans a swimming', +]; diff --git a/tests/_support/Language/ru/Language.php b/tests/_support/Language/ru/Language.php new file mode 100644 index 000000000000..d6c6632b7bb9 --- /dev/null +++ b/tests/_support/Language/ru/Language.php @@ -0,0 +1,5 @@ + 'Whatever this would be, translated', +]; diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php index c13069d57f57..537930f57fce 100644 --- a/tests/system/Language/LanguageTest.php +++ b/tests/system/Language/LanguageTest.php @@ -132,7 +132,7 @@ public function testLangAllowsOtherLocales() $str2 = lang('Language.languageGetLineInvalidArgumentException', [], 'ru'); $this->assertEquals('Get line must be a string or array of strings.', $str1); - $this->assertEquals('Language.languageGetLineInvalidArgumentException', $str2); + $this->assertEquals('Whatever this would be, translated', $str2); } //-------------------------------------------------------------------- @@ -155,7 +155,7 @@ public function testLangDoesntFormat() public function testLanguageDuplicateKey() { - $lang = new Language('en'); + $lang = new Language('en', false); $this->assertEquals('These are not the droids you are looking for', $lang->getLine('More.strongForce', [])); $this->assertEquals('I have a very bad feeling about this', $lang->getLine('More.cannotMove', [])); $this->assertEquals('Could not move file {0} to {1} ({2})', $lang->getLine('Files.cannotMove', [])); @@ -223,7 +223,7 @@ public function testPrioritizedLocator() $message = lang('Number.trillion', [], 'en'); $this->assertEquals(' lots', $message); // and we should have our new message too - $this->assertEquals(' bazillions', lang('Number.bazillion', [], 'en')); + $this->assertEquals(' bazillion', lang('Number.bazillion', [], 'en')); } //-------------------------------------------------------------------- @@ -268,4 +268,50 @@ public function testBundleUniqueKeys($bundle) $this->assertGreaterThan(0, count($messages)); } + //-------------------------------------------------------------------- + // Testing base locale vs variants + + public function testBaseFallbacks() + { + $language = Services::language('en-ZZ', false); + // key is in both base and variant; should pick variant + $this->assertEquals("It's made of cheese", $language->getLine('More.notaMoon')); + + // key is in base but not variant; should pick base + $this->assertEquals('I have a very bad feeling about this', $language->getLine('More.cannotMove')); + + // key is in variant but not base; should pick variant + $this->assertEquals('There is no try', $language->getLine('More.wisdom')); + + // key isn't in either base or variant; should return bad key + $this->assertEquals('More.shootMe', $language->getLine('More.shootMe')); + } + + //-------------------------------------------------------------------- + /** + * Testing base locale vs variants, with fallback to English. + * + * Key en ab ac-CD + * none N N N + * one N N Y + * two N Y N + * tre N Y Y + * for Y N N + * fiv Y N Y + * six Y Y N + * sev Y Y Y + */ + public function testAllTheWayFallbacks() + { + $language = Services::language('ab-CD', false); + $this->assertEquals('Allin.none', $language->getLine('Allin.none')); + $this->assertEquals('Pyramid of Giza', $language->getLine('Allin.one')); + $this->assertEquals('gluttony', $language->getLine('Allin.two')); + $this->assertEquals('Colossus of Rhodes', $language->getLine('Allin.tre')); + $this->assertEquals('four calling birds', $language->getLine('Allin.for')); + $this->assertEquals('Temple of Artemis', $language->getLine('Allin.fiv')); + $this->assertEquals('envy', $language->getLine('Allin.six')); + $this->assertEquals('Hanging Gardens of Babylon', $language->getLine('Allin.sev')); + } + } diff --git a/user_guide_src/source/outgoing/localization.rst b/user_guide_src/source/outgoing/localization.rst index 0262adccfee2..b5a3d745bc25 100644 --- a/user_guide_src/source/outgoing/localization.rst +++ b/user_guide_src/source/outgoing/localization.rst @@ -263,3 +263,36 @@ Language files also allow nested arrays to make working with lists, etc... easie // Displays "Apples, Bananas, Grapes, Lemons, Oranges, Strawberries" echo implode(', ', lang('Fruit.list')); + +Language Fallback +================= + +If you have a set of messages for a given locale, for instance +``Language/en/app.php``, you can add language variants for that locale, +each in its own folder, for instance ``Language/en-US/app.php``. + +You only need to provide values for those messages that would be +localized differently for that locale variant. Any missing message +definitions will be automatically pulled from the main locale settings. + +It gets better - the localization can fall all the way back to English, +in case new messages are added to the framework and you haven't had +a chance to translate them yet for your locale. + +So, if you are using the locale ``fr-CA``, then a localized +message will first be sought in ``Language/fr/CA``, then in +``Language/fr``, and finally in ``Language/en``. + +Message Translations +==================== + +We have an "official" set of translations in their +`own repository `_. + +You can download that repository, and copy its ``Language`` folder +into your ``app``. The incorporated translations will be automatically +picked up because the ``App`` namespace is mapped to your ``app`` folder. + +Alternately, you could use ``composer install codeigniter4/translations`` +inside your project, and the translated messages will be automatically picked +up because the ``Translations`` namespace gets mapped appropriately.