From e714a7cf3ed569a6183e09920402b37a4a0e8237 Mon Sep 17 00:00:00 2001 From: Jim Parry Date: Thu, 13 Dec 2018 08:28:56 -0800 Subject: [PATCH 1/3] Flesh out tests for Language --- system/Language/Language.php | 13 ++-- tests/_support/Language/en/Number.php | 31 ++++++++++ tests/system/Language/LanguageTest.php | 82 ++++++++++++++++++++++---- 3 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 tests/_support/Language/en/Number.php diff --git a/system/Language/Language.php b/system/Language/Language.php index 5f0c1fa1f9b2..438f33d92286 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -293,22 +293,19 @@ protected function load(string $file, string $locale, bool $return = false) */ protected function requireFile(string $path): array { - $files = Services::locator()->search($path); - + $files = Services::locator()->search($path); $strings = []; foreach ($files as $file) { - if (! is_file($file)) - { - continue; - } - // On some OS's we were seeing failures // on this command returning boolean instead // of array during testing, so we've removed // the require_once for now. - $strings[] = require $file; + if (is_file($file)) + { + $strings[] = require $file; + } } if (isset($strings[1])) diff --git a/tests/_support/Language/en/Number.php b/tests/_support/Language/en/Number.php new file mode 100644 index 000000000000..3625fea7aa9e --- /dev/null +++ b/tests/_support/Language/en/Number.php @@ -0,0 +1,31 @@ + 'TB', + 'gigabyteAbbr' => 'GB', + 'megabyteAbbr' => 'MB', + 'kilobyteAbbr' => 'KB', + 'bytes' => 'Bytes', + + // don't forget the space in front of these! + 'thousand' => ' thousand', + 'million' => ' million', + 'billion' => ' billion', + 'trillion' => ' lots', // over-rides a core setting + 'quadrillion' => ' quadrillion', + 'bazillion' => ' bazillion', // adds a new setting +]; diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php index 32a84a20e2c2..c13069d57f57 100644 --- a/tests/system/Language/LanguageTest.php +++ b/tests/system/Language/LanguageTest.php @@ -1,5 +1,7 @@ -assertEquals( - 'lay of the land', - $lang->getLine('equivalent.lieOfLand') + 'lay of the land', $lang->getLine('equivalent.lieOfLand') ); $this->assertEquals( - 'slowpoke', - $lang->getLine('equivalent.slowcoach') + 'slowpoke', $lang->getLine('equivalent.slowcoach') ); $this->assertEquals( - 'a new lease of life', - $lang->getLine('equivalent.leaseOfLife') + 'a new lease of life', $lang->getLine('equivalent.leaseOfLife') ); $this->assertEquals( - 'touch wood', - $lang->getLine('equivalent.touchWood') + 'touch wood', $lang->getLine('equivalent.touchWood') ); $this->assertEquals( - 'equivalent.unknown', - $lang->getLine('equivalent.unknown') + 'equivalent.unknown', $lang->getLine('equivalent.unknown') ); } @@ -208,4 +205,67 @@ public function testLanguageSameKeyAndFileName() $this->assertEquals('Another example', $lang->getLine('another.example')); } + + //-------------------------------------------------------------------- + + public function testGetLocale() + { + $language = Services::language('en', false); + $this->assertEquals('en', $language->getLocale()); + } + + //-------------------------------------------------------------------- + + public function testPrioritizedLocator() + { + $language = Services::language('en', false); + // this should load the replacement bundle of messages + $message = lang('Number.trillion', [], 'en'); + $this->assertEquals(' lots', $message); + // and we should have our new message too + $this->assertEquals(' bazillions', lang('Number.bazillion', [], 'en')); + } + + //-------------------------------------------------------------------- + + public function MessageBundles() + { + return [ + ['CLI'], + ['Cache'], + ['Cast'], + ['Core'], + ['Database'], + ['Email'], + ['Files'], + ['Filters'], + ['Format'], + ['HTTP'], + ['Images'], + ['Language'], + ['Log'], + ['Migrations'], + ['Number'], + ['Pager'], + ['Router'], + ['Session'], + ['Time'], + ['Validation'], + ['View'], + ]; + } + + /** + * There's not a whole lot that can be done with message bundles, + * but we can at least try loading them ... more accurate code coverage? + * + * @dataProvider MessageBundles + */ + public function testBundleUniqueKeys($bundle) + { + $language = Services::language('en', false); + $messages = require BASEPATH . 'Language/en/' . $bundle . '.php'; + $this->assertGreaterThan(0, count($messages)); + } + } From 981383d56a5e93d8c7170fb668701e37ae1db09a Mon Sep 17 00:00:00 2001 From: Jim Parry Date: Fri, 14 Dec 2018 00:39:13 -0800 Subject: [PATCH 2/3] Test, fix & enhance language fallback --- system/Language/Language.php | 18 ++++++- tests/_support/Language/ab-CD/Allin.php | 8 +++ tests/_support/Language/ab/Allin.php | 8 +++ tests/_support/Language/en-ZZ/More.php | 6 +++ tests/_support/Language/en/Allin.php | 8 +++ tests/_support/Language/ru/Language.php | 5 ++ tests/system/Language/LanguageTest.php | 52 +++++++++++++++++-- .../source/outgoing/localization.rst | 33 ++++++++++++ 8 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 tests/_support/Language/ab-CD/Allin.php create mode 100644 tests/_support/Language/ab/Allin.php create mode 100644 tests/_support/Language/en-ZZ/More.php create mode 100644 tests/_support/Language/en/Allin.php create mode 100644 tests/_support/Language/ru/Language.php 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. From 8321f4b77859dfce1e2d75cacd3d3d675c59a6cb Mon Sep 17 00:00:00 2001 From: Jim Parry Date: Fri, 14 Dec 2018 01:14:07 -0800 Subject: [PATCH 3/3] Update language test to avoid conflict --- tests/_support/Language/en/Core.php | 20 +++++++++++++++++ tests/_support/Language/en/Number.php | 31 -------------------------- tests/system/Language/LanguageTest.php | 6 ++--- 3 files changed, 23 insertions(+), 34 deletions(-) create mode 100644 tests/_support/Language/en/Core.php delete mode 100644 tests/_support/Language/en/Number.php diff --git a/tests/_support/Language/en/Core.php b/tests/_support/Language/en/Core.php new file mode 100644 index 000000000000..4d03510f2e90 --- /dev/null +++ b/tests/_support/Language/en/Core.php @@ -0,0 +1,20 @@ + '{0} extension could not be found.', + 'bazillion' => 'billions and billions', // adds a new setting +]; diff --git a/tests/_support/Language/en/Number.php b/tests/_support/Language/en/Number.php deleted file mode 100644 index 3625fea7aa9e..000000000000 --- a/tests/_support/Language/en/Number.php +++ /dev/null @@ -1,31 +0,0 @@ - 'TB', - 'gigabyteAbbr' => 'GB', - 'megabyteAbbr' => 'MB', - 'kilobyteAbbr' => 'KB', - 'bytes' => 'Bytes', - - // don't forget the space in front of these! - 'thousand' => ' thousand', - 'million' => ' million', - 'billion' => ' billion', - 'trillion' => ' lots', // over-rides a core setting - 'quadrillion' => ' quadrillion', - 'bazillion' => ' bazillion', // adds a new setting -]; diff --git a/tests/system/Language/LanguageTest.php b/tests/system/Language/LanguageTest.php index 537930f57fce..59848978feb5 100644 --- a/tests/system/Language/LanguageTest.php +++ b/tests/system/Language/LanguageTest.php @@ -220,10 +220,10 @@ public function testPrioritizedLocator() { $language = Services::language('en', false); // this should load the replacement bundle of messages - $message = lang('Number.trillion', [], 'en'); - $this->assertEquals(' lots', $message); + $message = lang('Core.missingExtension', [], 'en'); + $this->assertEquals('{0} extension could not be found.', $message); // and we should have our new message too - $this->assertEquals(' bazillion', lang('Number.bazillion', [], 'en')); + $this->assertEquals('billions and billions', lang('Core.bazillion', [], 'en')); } //--------------------------------------------------------------------