Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test, fix & enhance Language #1610

Merged
merged 3 commits into from
Dec 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions system/Language/Language.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php namespace CodeIgniter\Language;
<?php
namespace CodeIgniter\Language;

/**
* CodeIgniter
Expand Down Expand Up @@ -132,6 +133,12 @@ public function getLocale(): string
*/
public function getLine(string $line, array $args = [])
{
// ignore requests with no file specified
if (! strpos($line, '.'))
{
return $line;
}

// Parse out the file name and the actual alias.
// Will load the language file and strings.
[
Expand All @@ -153,6 +160,13 @@ public function getLine(string $line, array $args = [])
$output = $this->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))
Expand Down Expand Up @@ -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;
}

//--------------------------------------------------------------------
Expand All @@ -293,22 +307,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]))
Expand Down
8 changes: 8 additions & 0 deletions tests/_support/Language/ab-CD/Allin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
// seven wonders of the ancient world
return [
'one' => 'Pyramid of Giza',
'tre' => 'Colossus of Rhodes',
'fiv' => 'Temple of Artemis',
'sev' => 'Hanging Gardens of Babylon',
];
8 changes: 8 additions & 0 deletions tests/_support/Language/ab/Allin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
// seven deadly sins
return [
'two' => 'gluttony',
'tre' => 'greed',
'six' => 'envy',
'sev' => 'pride',
];
6 changes: 6 additions & 0 deletions tests/_support/Language/en-ZZ/More.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
return [
'strongForce' => 'These are not the droids you are looking for',
'notaMoon' => "It's made of cheese",
'wisdom' => 'There is no try',
];
8 changes: 8 additions & 0 deletions tests/_support/Language/en/Allin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
// 12 days of Christmas
return [
'for' => 'four calling birds',
'fiv' => 'five golden rings',
'six' => 'six geese a laying',
'sev' => 'seven swans a swimming',
];
20 changes: 20 additions & 0 deletions tests/_support/Language/en/Core.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/**
* Core language strings.
*
* @package CodeIgniter
* @author CodeIgniter Dev Team
* @copyright 2014-2018 British Columbia Institute of Technology (https://bcit.ca/)
* @license https://opensource.org/licenses/MIT MIT License
* @link https://codeigniter.com
* @since Version 3.0.0
* @filesource
*
* @codeCoverageIgnore
*/

// looking for a system message not likely to be part of unit testing
return [
'missingExtension' => '{0} extension could not be found.',
'bazillion' => 'billions and billions', // adds a new setting
];
5 changes: 5 additions & 0 deletions tests/_support/Language/ru/Language.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
'languageGetLineInvalidArgumentException' => 'Whatever this would be, translated',
];
132 changes: 119 additions & 13 deletions tests/system/Language/LanguageTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php namespace CodeIgniter\Language;
<?php
namespace CodeIgniter\Language;

use Config\Services;
use Tests\Support\Language\MockLanguage;
use Tests\Support\Language\SecondMockLanguage;

Expand Down Expand Up @@ -44,24 +46,19 @@ public function testGetLineReturnsFallbackLine()
], 'en-US');

$this->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')
);
}

Expand Down Expand Up @@ -135,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);
}

//--------------------------------------------------------------------
Expand All @@ -158,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', []));
Expand Down Expand Up @@ -208,4 +205,113 @@ 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('Core.missingExtension', [], 'en');
$this->assertEquals('{0} extension could not be found.', $message);
// and we should have our new message too
$this->assertEquals('billions and billions', lang('Core.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));
}

//--------------------------------------------------------------------
// 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'));
}

}
33 changes: 33 additions & 0 deletions user_guide_src/source/outgoing/localization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/codeigniter4/translations>`_.

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.