From 897ec45572362bdc61a3b358e96b0fb4216ce6c5 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 31 Aug 2019 19:48:42 -0400 Subject: [PATCH 01/40] PHP7-ize translation --- src/MessageTranslator.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/MessageTranslator.php b/src/MessageTranslator.php index 11645eb..9c5b39e 100644 --- a/src/MessageTranslator.php +++ b/src/MessageTranslator.php @@ -55,7 +55,7 @@ public function __construct(array $items = []) * * @return string The translated message. */ - public function translate($messageKey, $placeholders = []) + public function translate(string $messageKey, $placeholders = []): string { // Get the correct message from the specified key $message = $this->getMessageFromKey($messageKey, $placeholders); @@ -76,7 +76,7 @@ public function translate($messageKey, $placeholders = []) * * @return string The message string */ - protected function getMessageFromKey($messageKey, &$placeholders) + protected function getMessageFromKey(string $messageKey, &$placeholders): string { // If we can't find a match, return $messageKey if (!$this->has($messageKey)) { @@ -151,7 +151,7 @@ protected function getMessageFromKey($messageKey, &$placeholders) * * @return string */ - protected function getPluralKey(array $messageArray) + protected function getPluralKey(array $messageArray): string { if (isset($messageArray['@PLURAL'])) { return $messageArray['@PLURAL']; @@ -168,7 +168,7 @@ protected function getPluralKey(array $messageArray) * * @return int|null The number, null if not found */ - protected function getPluralValue($placeholders, $pluralKey) + protected function getPluralValue($placeholders, string $pluralKey): ?int { if (isset($placeholders[$pluralKey])) { return (int) $placeholders[$pluralKey]; @@ -179,6 +179,7 @@ protected function getPluralValue($placeholders, $pluralKey) } // Null will be returned + return null; } /** @@ -188,9 +189,9 @@ protected function getPluralValue($placeholders, $pluralKey) * @param array $messageArray The array with all the form inside ($pluralRule => $message) * @param int $pluralValue The numeric value used to select the correct message * - * @return int Returns which key from $messageArray to use + * @return int|null Returns which key from $messageArray to use */ - protected function getPluralMessageKey(array $messageArray, $pluralValue) + protected function getPluralMessageKey(array $messageArray, int $pluralValue): ?int { // Bypass the rules for a value of "0" so that "0 users" may be displayed as "No users". if ($pluralValue == 0 && isset($messageArray[0])) { @@ -216,6 +217,7 @@ protected function getPluralMessageKey(array $messageArray, $pluralValue) } // If no key was found, null will be returned + return null; } /** @@ -227,7 +229,7 @@ protected function getPluralMessageKey(array $messageArray, $pluralValue) * * @return string The message with replaced placeholders */ - protected function parsePlaceHolders($message, array $placeholders) + protected function parsePlaceHolders(string $message, array $placeholders): string { // Interpolate translatable placeholders values. This allows to // pre-translate placeholder which value starts with the `&` caracter From 18d02ae11df5ab2a26f9662d7c437c9a6178a4fc Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 15 Oct 2019 22:05:18 -0400 Subject: [PATCH 02/40] Added test data --- .gitignore | 1 + tests/data/account/locale/fr_FR/config.yaml | 9 ++ tests/data/account/locale/fr_FR/test.php | 126 ++++++++++++++++++++ tests/data/core/locale/en_US/config.yaml | 8 ++ tests/data/core/locale/es_ES/config.yaml | 9 ++ tests/data/core/locale/fr_FR/config.yaml | 8 ++ tests/data/fr_CA/locale/fr_CA/config.yaml | 9 ++ tests/data/fr_CA/locale/fr_CA/test.php | 126 ++++++++++++++++++++ 8 files changed, 296 insertions(+) create mode 100644 tests/data/account/locale/fr_FR/config.yaml create mode 100644 tests/data/account/locale/fr_FR/test.php create mode 100644 tests/data/core/locale/en_US/config.yaml create mode 100644 tests/data/core/locale/es_ES/config.yaml create mode 100644 tests/data/core/locale/fr_FR/config.yaml create mode 100644 tests/data/fr_CA/locale/fr_CA/config.yaml create mode 100644 tests/data/fr_CA/locale/fr_CA/test.php diff --git a/.gitignore b/.gitignore index caa2e01..170c7e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock *.komodoproject .php_cs.cache _meta +.serenata diff --git a/tests/data/account/locale/fr_FR/config.yaml b/tests/data/account/locale/fr_FR/config.yaml new file mode 100644 index 0000000..4606d22 --- /dev/null +++ b/tests/data/account/locale/fr_FR/config.yaml @@ -0,0 +1,9 @@ +name: French +localized_name: Français +authors: + - Foo Bar + - Bar Foo +options: + plural: 2 +parents: + - en_US diff --git a/tests/data/account/locale/fr_FR/test.php b/tests/data/account/locale/fr_FR/test.php new file mode 100644 index 0000000..0e7c591 --- /dev/null +++ b/tests/data/account/locale/fr_FR/test.php @@ -0,0 +1,126 @@ + 2, //Required to get the right rule. French is 2, english is 1 + + 'USERNAME' => 'Nom d\'utilisateur', //Note the espace `\` caracter here. Won't be displayed in the test + + //"BASE_FALLBACK" => "Langue de secours", //We want to test if the english string will be displayed here + + 'ACCOUNT' => [ + '@TRANSLATION' => "Compte de l'utilisateur", //Don't need to escape if using double quote `"` + 'ALT' => 'Profil', + ], + + // Colors + 'COLOR' => [ + //Substrings + 'BLACK' => 'noir', + 'RED' => 'rouge', + 'WHITE' => 'blanc', + + //Plurals + 0 => 'couleur', + 1 => 'couleur', + 2 => 'couleurs', + ], + + // Cars + 'CAR' => [ + //Plurals + 1 => 'voiture', + 2 => 'voitures', + + //Substrings + 'GAS' => 'à essence', + 'EV' => [ + //"@TRANSLATION" => "électrique", //Can't work for french ! + // But since French is loaded on top to English and English have this one defined + // it will return the english string if we want to translate "EV" without a plural value. + // So we need to get rid of the English string : + '@TRANSLATION' => null, + + //We will pluralize instead + 1 => 'électrique', + 2 => 'électriques', + + //Sub-Substring + 'FULL' => '100% électrique', + 'HYBRID' => 'hybride', + 'PLUGIN_HYBRID' => 'hybride branchable', + ], + 'HYDROGEN' => "à l'hydrogène", + ], + 'X_CARS' => [ + 0 => 'aucune voiture', + 1 => 'une voiture', + 2 => '{{plural}} voitures', + ], + + // Placeholder strings + 'MY_CAR_STRING' => 'Je conduit une {{my_car}} de couleur {{color}}', + 'MY_CAR_MAKE' => 'Ma voiture est une {{car_make}}', + 'MY_CAR_YEAR' => "J'ai acheté ma voiture en {{year}}", + 'MY_CARS' => "J'ai {{x_cars}}", + + // Plural with placeholder + 'MY_EV_CARS' => [ + '@TRANSLATION' => 'Mes voitures électriques', + 1 => 'Le chat a une {{&CAR}} {{type}}', + 2 => 'Le chat a {{plural}} {{&CAR}} {{type}}', + ], + + // Custom plural key with no "zero" case. + // In english, "2" should be used when the plural value is zero. In french, "1" should be used + 'X_HUNGRY_CATS' => [ + '@PLURAL' => 'num', + 1 => '{{num}} chat affamé', + 2 => '{{num}} chats affamés', + ], + + // Min/max placeholder where the + 'TEST_LIMIT' => 'Votre test doit être entre {{min}} et {{max}} patates.', + 'MIN' => 'minimum', + //"MAX" => "maximum" //Leave disabled for tests + + // Empty array + 'EMPTY' => [ + + ], + + // Missing one rule + 'X_RULES' => [ + 0 => 'aucune règle', + 1 => '{{plural}} règle', + //2 => '{{plural}} règles', //Leave disabled for tests + ], + + // Missing all rules + 'X_BANANAS' => [ + 0 => 'aucune banane', + //1 => '{{plural}} banane', //Leave disabled for tests + //2 => '{{plural}} bananes', //Leave disabled for tests + ], + + // No rules are followed + 'X_DOGS' => [ + 5 => 'cinq chiens', + 101 => '101 Dalmatiens', + '1000' => 'Une tempête de chiens', + ], + + // keys as strings + 'X_TABLES' => [ + '0' => 'aucune table', + '1' => 'une table', + '2' => '{{plural}} tables', + ], +]; diff --git a/tests/data/core/locale/en_US/config.yaml b/tests/data/core/locale/en_US/config.yaml new file mode 100644 index 0000000..09f9d63 --- /dev/null +++ b/tests/data/core/locale/en_US/config.yaml @@ -0,0 +1,8 @@ +name: English +localized_name: English +authors: + - John Appleseed +options: + plural: 1 +parents: +- en_US diff --git a/tests/data/core/locale/es_ES/config.yaml b/tests/data/core/locale/es_ES/config.yaml new file mode 100644 index 0000000..aa2dd17 --- /dev/null +++ b/tests/data/core/locale/es_ES/config.yaml @@ -0,0 +1,9 @@ +name: Spanish +localized_name: Español +authors: + - Salma Hayek + - Selena Gomez +options: + plural: 1 +parents: +- en_US diff --git a/tests/data/core/locale/fr_FR/config.yaml b/tests/data/core/locale/fr_FR/config.yaml new file mode 100644 index 0000000..2be1d1c --- /dev/null +++ b/tests/data/core/locale/fr_FR/config.yaml @@ -0,0 +1,8 @@ +name: French +localized_name: Français +authors: + - Foo Bar +options: + plural: 2 +parents: +- en_US diff --git a/tests/data/fr_CA/locale/fr_CA/config.yaml b/tests/data/fr_CA/locale/fr_CA/config.yaml new file mode 100644 index 0000000..c7cf5bf --- /dev/null +++ b/tests/data/fr_CA/locale/fr_CA/config.yaml @@ -0,0 +1,9 @@ +name: French Canadian +localized_name: Français Canadien +authors: + - Foo Bar + - Bar Foo +options: + plural: 2 +parents: + - fr_FR diff --git a/tests/data/fr_CA/locale/fr_CA/test.php b/tests/data/fr_CA/locale/fr_CA/test.php new file mode 100644 index 0000000..0e7c591 --- /dev/null +++ b/tests/data/fr_CA/locale/fr_CA/test.php @@ -0,0 +1,126 @@ + 2, //Required to get the right rule. French is 2, english is 1 + + 'USERNAME' => 'Nom d\'utilisateur', //Note the espace `\` caracter here. Won't be displayed in the test + + //"BASE_FALLBACK" => "Langue de secours", //We want to test if the english string will be displayed here + + 'ACCOUNT' => [ + '@TRANSLATION' => "Compte de l'utilisateur", //Don't need to escape if using double quote `"` + 'ALT' => 'Profil', + ], + + // Colors + 'COLOR' => [ + //Substrings + 'BLACK' => 'noir', + 'RED' => 'rouge', + 'WHITE' => 'blanc', + + //Plurals + 0 => 'couleur', + 1 => 'couleur', + 2 => 'couleurs', + ], + + // Cars + 'CAR' => [ + //Plurals + 1 => 'voiture', + 2 => 'voitures', + + //Substrings + 'GAS' => 'à essence', + 'EV' => [ + //"@TRANSLATION" => "électrique", //Can't work for french ! + // But since French is loaded on top to English and English have this one defined + // it will return the english string if we want to translate "EV" without a plural value. + // So we need to get rid of the English string : + '@TRANSLATION' => null, + + //We will pluralize instead + 1 => 'électrique', + 2 => 'électriques', + + //Sub-Substring + 'FULL' => '100% électrique', + 'HYBRID' => 'hybride', + 'PLUGIN_HYBRID' => 'hybride branchable', + ], + 'HYDROGEN' => "à l'hydrogène", + ], + 'X_CARS' => [ + 0 => 'aucune voiture', + 1 => 'une voiture', + 2 => '{{plural}} voitures', + ], + + // Placeholder strings + 'MY_CAR_STRING' => 'Je conduit une {{my_car}} de couleur {{color}}', + 'MY_CAR_MAKE' => 'Ma voiture est une {{car_make}}', + 'MY_CAR_YEAR' => "J'ai acheté ma voiture en {{year}}", + 'MY_CARS' => "J'ai {{x_cars}}", + + // Plural with placeholder + 'MY_EV_CARS' => [ + '@TRANSLATION' => 'Mes voitures électriques', + 1 => 'Le chat a une {{&CAR}} {{type}}', + 2 => 'Le chat a {{plural}} {{&CAR}} {{type}}', + ], + + // Custom plural key with no "zero" case. + // In english, "2" should be used when the plural value is zero. In french, "1" should be used + 'X_HUNGRY_CATS' => [ + '@PLURAL' => 'num', + 1 => '{{num}} chat affamé', + 2 => '{{num}} chats affamés', + ], + + // Min/max placeholder where the + 'TEST_LIMIT' => 'Votre test doit être entre {{min}} et {{max}} patates.', + 'MIN' => 'minimum', + //"MAX" => "maximum" //Leave disabled for tests + + // Empty array + 'EMPTY' => [ + + ], + + // Missing one rule + 'X_RULES' => [ + 0 => 'aucune règle', + 1 => '{{plural}} règle', + //2 => '{{plural}} règles', //Leave disabled for tests + ], + + // Missing all rules + 'X_BANANAS' => [ + 0 => 'aucune banane', + //1 => '{{plural}} banane', //Leave disabled for tests + //2 => '{{plural}} bananes', //Leave disabled for tests + ], + + // No rules are followed + 'X_DOGS' => [ + 5 => 'cinq chiens', + 101 => '101 Dalmatiens', + '1000' => 'Une tempête de chiens', + ], + + // keys as strings + 'X_TABLES' => [ + '0' => 'aucune table', + '1' => 'une table', + '2' => '{{plural}} tables', + ], +]; From b2236a12343991d10198fe16bcc3c28e32e9bbc7 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 15 Oct 2019 22:05:53 -0400 Subject: [PATCH 03/40] Added Locale Class --- src/Locale.php | 160 ++++++++++++++++++++++++++++++++++++++++ src/LocaleInterface.php | 62 ++++++++++++++++ tests/LocaleTest.php | 145 ++++++++++++++++++++++++++++++++++++ 3 files changed, 367 insertions(+) create mode 100644 src/Locale.php create mode 100644 src/LocaleInterface.php create mode 100644 tests/LocaleTest.php diff --git a/src/Locale.php b/src/Locale.php new file mode 100644 index 0000000..7708742 --- /dev/null +++ b/src/Locale.php @@ -0,0 +1,160 @@ + translation" data matrix + */ + protected $dictionary = []; + + /** + * Create locale class + * + * @param string $identifier The locale identifier (ie. "en_US") + * @param string $configFile The path to the locale config file + */ + public function __construct(string $identifier, string $configFile) + { + $this->identifier = $identifier; + $this->configFile = $configFile; + + // Load locale config + $this->loadConfig(); + } + + /** + * Loads the config into the class property + * + * @throws \UserFrosting\Support\Exception\FileNotFoundException if config file not found + */ + protected function loadConfig(): void + { + $loader = new YamlFileLoader($this->configFile); + $this->config = $loader->load(false); + } + + /** + * Returns the list of authors of the locale + * + * @return string[] The list of authors + */ + public function getAuthors(): array + { + return $this->config['authors']; + } + + /** + * Returns all loaded locale Key => Translation data dictionary + * + * @return string[] The locale dictionnary + */ + public function getDictionary(): array + { + if (empty($this->dictionary)) { + $this->loadDictionary(); + } + + return $this->dictionary; + } + + /** + * Returns defined configuration file + * + * @return string + */ + public function getConfigFile(): string + { + return $this->configFile; + } + + /** + * Returns the locale indentifier + * + * @return string + */ + public function getIndentifier(): string + { + return $this->identifier; + } + + /** + * Return the raw configuration data + * + * @return array + */ + public function getConfig(): array + { + return $this->config; + } + + /** + * Return the raw configuration data + * + * @return array + */ + public function getDependentLocales(): array + { + return $this->config['parents']; + } + + /** + * Return the name of the locale, in English form + * + * @return string + */ + public function getName(): string + { + return $this->config['name']; + } + + /** + * Return the localized version of the locale name + * + * @return string + */ + public function getLocalizedName(): string + { + return $this->config['localized_name']; + } + + protected function loadDictionary(): array + { + $dictionary = []; + + return $this->dictionary = $dictionary; + } +} diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php new file mode 100644 index 0000000..088bc8f --- /dev/null +++ b/src/LocaleInterface.php @@ -0,0 +1,62 @@ + Translation data dictionary + * + * @return string[] The locale dictionnary + */ + public function getDictionary(): array; + + /** + * Returns defined configuration file + * + * @return string + */ + public function getConfigFile(): string; + + /** + * Returns the locale indentifier + * + * @return string + */ + public function getIndentifier(): string; + + /** + * Return the raw configuration data + * + * @return array + */ + public function getConfig(): array; +} diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php new file mode 100644 index 0000000..ce45ac4 --- /dev/null +++ b/tests/LocaleTest.php @@ -0,0 +1,145 @@ +basePath = __DIR__.'/data'; + $this->locator = new ResourceLocator($this->basePath); + + $this->locator->registerStream('locale'); + + // Add them one at a time to simulate how they are added in SprinkleManager + $this->locator->registerLocation('core'); + $this->locator->registerLocation('account'); + $this->locator->registerLocation('admin'); + } + + public function testConstructor(): Locale + { + $locale = new Locale('fr_FR', 'locale://fr_FR/config.yaml'); + $this->assertInstanceOf(LocaleInterface::class, $locale); + + return $locale; + } + + public function testConstructorWithNotFoundPath() + { + $this->expectException(FileNotFoundException::class); + $locale = new Locale('fr_FR', 'locale://fr_FR/dontexist.yaml'); + } + + /** + * @depends testConstructor + */ + public function testGetConfigFile(Locale $locale) + { + $data = $locale->getConfigFile(); + $this->assertInternalType('string', $data); + + $this->assertSame('locale://fr_FR/config.yaml', $data); + } + + /** + * @depends testConstructor + */ + public function testGetIndentifier(Locale $locale) + { + $data = $locale->getIndentifier(); + $this->assertInternalType('string', $data); + + $this->assertSame('fr_FR', $data); + } + + /** + * @depends testConstructor + */ + public function testGetConfig(Locale $locale) + { + $data = $locale->getConfig(); + $this->assertInternalType('array', $data); + + $this->assertSame([ + 'name' => 'French', + 'localized_name' => 'Français', + 'authors' => [ + 'Foo Bar', + 'Bar Foo', // Not available in `core` version + ], + 'options' => [ + 'plural' => 2, + ], + 'parents' => [ + 'en_US' + ] + ], $data); + } + + /** + * @depends testConstructor + * @depends testGetConfig + */ + public function testGetAuthors(Locale $locale) + { + $data = $locale->getAuthors(); + $this->assertInternalType('array', $data); + + $this->assertSame([ + 'Foo Bar', + 'Bar Foo', // Not available in `core` version + ], $data); + + $this->assertSame($locale->getConfig()['authors'], $data); + } + + /** + * @depends testConstructor + * @depends testGetConfig + */ + public function testGetDetails(Locale $locale) + { + //getName + $this->assertInternalType('string', $locale->getName()); + $this->assertSame('French', $locale->getName()); + + //getLocalizedName + $this->assertInternalType('string', $locale->getLocalizedName()); + $this->assertSame('Français', $locale->getLocalizedName()); + + //getDependentLocales + $this->assertInternalType('array', $locale->getDependentLocales()); + $this->assertSame(['en_US'], $locale->getDependentLocales()); + } + + /** + * @depends testConstructor + * @depends testGetConfig + */ + /*public function testGetDictionary(Locale $locale) + { + $dictionary = $locale->getDictionary(); + $this->assertInternalType('array', $dictionary); + + + }*/ +} From b12ff67bfeb21a3a8f0854f076d2b0caee636490 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 15 Oct 2019 22:08:39 -0400 Subject: [PATCH 04/40] Fix styling --- src/Locale.php | 20 ++++++++++---------- src/LocaleInterface.php | 12 ++++++------ tests/LocaleTest.php | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Locale.php b/src/Locale.php index 7708742..5aa6925 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -42,7 +42,7 @@ class Locale implements LocaleInterface protected $dictionary = []; /** - * Create locale class + * Create locale class. * * @param string $identifier The locale identifier (ie. "en_US") * @param string $configFile The path to the locale config file @@ -57,7 +57,7 @@ public function __construct(string $identifier, string $configFile) } /** - * Loads the config into the class property + * Loads the config into the class property. * * @throws \UserFrosting\Support\Exception\FileNotFoundException if config file not found */ @@ -68,7 +68,7 @@ protected function loadConfig(): void } /** - * Returns the list of authors of the locale + * Returns the list of authors of the locale. * * @return string[] The list of authors */ @@ -78,7 +78,7 @@ public function getAuthors(): array } /** - * Returns all loaded locale Key => Translation data dictionary + * Returns all loaded locale Key => Translation data dictionary. * * @return string[] The locale dictionnary */ @@ -92,7 +92,7 @@ public function getDictionary(): array } /** - * Returns defined configuration file + * Returns defined configuration file. * * @return string */ @@ -102,7 +102,7 @@ public function getConfigFile(): string } /** - * Returns the locale indentifier + * Returns the locale indentifier. * * @return string */ @@ -112,7 +112,7 @@ public function getIndentifier(): string } /** - * Return the raw configuration data + * Return the raw configuration data. * * @return array */ @@ -122,7 +122,7 @@ public function getConfig(): array } /** - * Return the raw configuration data + * Return the raw configuration data. * * @return array */ @@ -132,7 +132,7 @@ public function getDependentLocales(): array } /** - * Return the name of the locale, in English form + * Return the name of the locale, in English form. * * @return string */ @@ -142,7 +142,7 @@ public function getName(): string } /** - * Return the localized version of the locale name + * Return the localized version of the locale name. * * @return string */ diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index 088bc8f..4a784d6 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -18,7 +18,7 @@ interface LocaleInterface { /** - * Create locale class + * Create locale class. * * @param string $identifier The locale identifier (ie. "en_US") * @param string $configFile The path to the locale config file @@ -26,35 +26,35 @@ interface LocaleInterface public function __construct(string $identifier, string $configFile); /** - * Returns the list of authors of the locale + * Returns the list of authors of the locale. * * @return string[] The list of authors */ public function getAuthors(): array; /** - * Returns all loaded locale Key => Translation data dictionary + * Returns all loaded locale Key => Translation data dictionary. * * @return string[] The locale dictionnary */ public function getDictionary(): array; /** - * Returns defined configuration file + * Returns defined configuration file. * * @return string */ public function getConfigFile(): string; /** - * Returns the locale indentifier + * Returns the locale indentifier. * * @return string */ public function getIndentifier(): string; /** - * Return the raw configuration data + * Return the raw configuration data. * * @return array */ diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index ce45ac4..eb3e31d 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -13,8 +13,8 @@ use PHPUnit\Framework\TestCase; use UserFrosting\I18n\Locale; use UserFrosting\I18n\LocaleInterface; -use UserFrosting\UniformResourceLocator\ResourceLocator; use UserFrosting\Support\Exception\FileNotFoundException; +use UserFrosting\UniformResourceLocator\ResourceLocator; class LocaleTest extends TestCase { @@ -90,8 +90,8 @@ public function testGetConfig(Locale $locale) 'plural' => 2, ], 'parents' => [ - 'en_US' - ] + 'en_US', + ], ], $data); } @@ -131,7 +131,7 @@ public function testGetDetails(Locale $locale) $this->assertSame(['en_US'], $locale->getDependentLocales()); } - /** + /* * @depends testConstructor * @depends testGetConfig */ From 58ee1a634568a846bebcecc1050536607affa3de Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Thu, 17 Oct 2019 23:11:33 -0400 Subject: [PATCH 05/40] Added first draft of the Dictionary --- composer.json | 1 + src/Dictionary.php | 140 +++++++++++++++++++++++ src/DictionaryInterface.php | 28 +++++ src/Locale.php | 28 +---- src/LocaleInterface.php | 28 +++-- src/LocalePathBuilder.php | 134 ---------------------- tests/DictionaryTest.php | 89 ++++++++++++++ tests/LocalePathBuilderTest.php | 102 ----------------- tests/LocaleTest.php | 6 + tests/data/core/locale/es_ES/config.yaml | 1 - tests/data/core/locale/es_ES/foo/bar.php | 13 +++ 11 files changed, 299 insertions(+), 271 deletions(-) create mode 100644 src/Dictionary.php create mode 100644 src/DictionaryInterface.php delete mode 100644 src/LocalePathBuilder.php create mode 100644 tests/DictionaryTest.php delete mode 100644 tests/LocalePathBuilderTest.php create mode 100644 tests/data/core/locale/es_ES/foo/bar.php diff --git a/composer.json b/composer.json index 2e571b9..c7415a4 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", + "mockery/mockery": "^1.2", "phpunit/phpunit": "^7.5" }, "autoload": { diff --git a/src/Dictionary.php b/src/Dictionary.php new file mode 100644 index 0000000..fb0dc7e --- /dev/null +++ b/src/Dictionary.php @@ -0,0 +1,140 @@ + translation" data matrix + * + * @author Louis Charette + */ +class Dictionary implements DictionaryInterface +{ + /** + * @var LocaleInterface + */ + protected $locale; + + /** + * @var ResourceLocatorInterface + */ + protected $locator; + + /** + * @var array Locale "Key => translation" data matrix + */ + protected $dictionary = []; + + /** + * @param LocaleInterface $locale + * @param ResourceLocatorInterface $locator + */ + public function __construct(LocaleInterface $locale, ResourceLocatorInterface $locator) + { + $this->locale = $locale; + $this->locator = $locator; + } + + /** + * Returns all loaded locale Key => Translation data dictionary. + * Won't load the whole thing twice if already loaded in the class. + * + * @return string[] The locale dictionnary + */ + public function getDictionary(): array + { + if (empty($this->dictionary)) { + $this->dictionary = $this->loadDictionary(); + } + + return $this->dictionary; + } + + /** + * Load the dictionnary from file + * + * @return array The locale dictionnary + */ + protected function loadDictionary(): array + { + // Get list of files to load + $files = $this->getDictionaryFiles(); + + // Load all files content + $loader = new ArrayFileLoader($files); + return $loader->load(); + } + + /** + * Returns a list of files to load + * @return array[Resource] + */ + protected function getDictionaryFiles(): array + { + $files = []; + + // First, load all parents locales + $parents = $this->locale->getDependentLocales(); + + if (!empty($parents)) { + foreach ($parents as $parent) + { + $parentLocale = new Locale($parent, "locale://$parent/config.yaml"); + //TODO : Recursively load dictionnary instead, cause a dependant can have dependencies + $files = array_merge($files, $this->getFilesForLocale($parentLocale)); + } + } + + // Now get for main locale + $files = array_merge($files, $this->getFilesForLocale($this->locale)); + + // Only keep .php files + return $this->filterDictionaryFiles($files); + } + + /** + * Remove config files from locator results + * + * @param array $files + * @return array[Resource] + */ + protected function filterDictionaryFiles(array $files): array + { + //TODO : Use Array_filter + + $filtered = []; + + foreach ($files as $file) { + if ($file->getExtension() == "php") { + $filtered[] = $file; + } + } + + return $filtered; + } + + /** + * List all files for a given locale using the locator + * + * @param LocaleInterface $locale + * @return array[Resource] + */ + protected function getFilesForLocale(LocaleInterface $locale): array + { + return $this->locator->listResources('locale://' . $locale->getIndentifier(), true); + } +} diff --git a/src/DictionaryInterface.php b/src/DictionaryInterface.php new file mode 100644 index 0000000..d3528f5 --- /dev/null +++ b/src/DictionaryInterface.php @@ -0,0 +1,28 @@ + translation" data matrix + * + * @author Louis Charette + */ +interface DictionaryInterface +{ + /** + * Returns all loaded locale Key => Translation data dictionary. + * + * @return string[] The locale dictionnary + */ + public function getDictionary(): array; +} diff --git a/src/Locale.php b/src/Locale.php index 5aa6925..4b165b6 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -36,11 +36,6 @@ class Locale implements LocaleInterface */ protected $config; - /** - * @var array Locale "Key => translation" data matrix - */ - protected $dictionary = []; - /** * Create locale class. * @@ -77,20 +72,6 @@ public function getAuthors(): array return $this->config['authors']; } - /** - * Returns all loaded locale Key => Translation data dictionary. - * - * @return string[] The locale dictionnary - */ - public function getDictionary(): array - { - if (empty($this->dictionary)) { - $this->loadDictionary(); - } - - return $this->dictionary; - } - /** * Returns defined configuration file. * @@ -126,7 +107,7 @@ public function getConfig(): array * * @return array */ - public function getDependentLocales(): array + public function getDependentLocales(): ?array { return $this->config['parents']; } @@ -150,11 +131,4 @@ public function getLocalizedName(): string { return $this->config['localized_name']; } - - protected function loadDictionary(): array - { - $dictionary = []; - - return $this->dictionary = $dictionary; - } } diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index 4a784d6..0976595 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -32,13 +32,6 @@ public function __construct(string $identifier, string $configFile); */ public function getAuthors(): array; - /** - * Returns all loaded locale Key => Translation data dictionary. - * - * @return string[] The locale dictionnary - */ - public function getDictionary(): array; - /** * Returns defined configuration file. * @@ -59,4 +52,25 @@ public function getIndentifier(): string; * @return array */ public function getConfig(): array; + + /** + * Return the raw configuration data. + * + * @return array + */ + public function getDependentLocales(): ?array; + + /** + * Return the name of the locale, in English form. + * + * @return string + */ + public function getName(): string; + + /** + * Return the localized version of the locale name. + * + * @return string + */ + public function getLocalizedName(): string; } diff --git a/src/LocalePathBuilder.php b/src/LocalePathBuilder.php deleted file mode 100644 index e7d1b0e..0000000 --- a/src/LocalePathBuilder.php +++ /dev/null @@ -1,134 +0,0 @@ -setLocales($locales); - - parent::__construct($locator, $uri); - } - - /** - * Glob together all translation files for the current locales. - * - * @return string[] - */ - public function buildPaths() - { - // Get all paths from the locator that match the uri. - // Put them in reverse order to allow later files to override earlier files. - $searchPaths = array_reverse($this->locator->findResources($this->uri, true, true)); - - $filePaths = []; - - foreach ($this->locales as $locale) { - // Make sure it's a valid string before loading - if (is_string($locale) && $locale != '') { - $localePaths = $this->buildLocalePaths($searchPaths, trim($locale)); - $filePaths = array_merge($filePaths, $localePaths); - } - } - - return $filePaths; - } - - /** - * Adds provides locales to the end of the current locales list. - * Note that locale preference is ascending. - * - * @param string|string[] $locales - * - * @return $this - */ - public function addLocales($locales = []) - { - //So we can accept strings argument also - if (!is_array($locales)) { - $locales = [$locales]; - } - - // Add the new locales to the end - $this->locales = array_merge($this->locales, $locales); - - // Remove any duplicates, preserving only the last instance - $this->locales = array_reverse(array_unique(array_reverse($this->locales))); - - return $this; - } - - /** - * Returns list of locales. - * - * @return string[] - */ - public function getLocales() - { - return $this->locales; - } - - /** - * Sets locales. - * Note that locale preference is ascending. - * - * @param string|string[] $locales - */ - public function setLocales($locales = []) - { - $this->locales = []; - $this->addLocales($locales); - - return $this; - } - - /** - * Construct paths to all locale files for a given locale. - * - * @param string[] $searchPaths - * @param string $locale - */ - protected function buildLocalePaths($searchPaths, $locale) - { - $filePaths = []; - - // Search all paths for the specified locale - foreach ($searchPaths as $path) { - $localePath = rtrim($path, '/\\').'/'.$locale; - // Grab all php files in the locale directory - $globs = glob($localePath.'/*.php'); - $filePaths = array_merge($filePaths, $globs); - } - - return $filePaths; - } -} diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php new file mode 100644 index 0000000..8f90160 --- /dev/null +++ b/tests/DictionaryTest.php @@ -0,0 +1,89 @@ +basePath = __DIR__.'/data'; + $this->locator = new ResourceLocator($this->basePath); + + $this->locator->registerStream('locale'); + + // Add them one at a time to simulate how they are added in SprinkleManager + $this->locator->registerLocation('core'); + $this->locator->registerLocation('account'); + $this->locator->registerLocation('admin'); + } + + public function tearDown() + { + Mockery::close(); + } + + public function testConstructor() + { + /** @var LocaleInterface */ + $locale = Mockery::mock(Locale::class); + + $dictionary = new Dictionary($locale, $this->locator); + $this->assertInstanceOf(DictionaryInterface::class, $dictionary); + } + + /** + * @depends testConstructor + */ + /*public function testGetDictionary_withNoDependentLocaleNoData() + { + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([]); + + $dictionary = new Dictionary($locale, $this->locator); + + $this->assertInternalType('array', $dictionary->getDictionary()); + $this->assertEquals('array', $dictionary->getDictionary()); + }*/ + + /** + * @depends testConstructor + */ + public function testGetDictionary_withRealLocale() + { + $locale = new Locale('es_ES', 'locale://es_ES/config.yaml'); + $dictionary = new Dictionary($locale, $this->locator); + + $expectedResult = [ + 'X_CARS' => [ + 1 => '{{plural}} coche', + 2 => '{{plural}} coches', + ], + 'FOO' => 'BAR' + ]; + + $data = $dictionary->getDictionary(); + + $this->assertInternalType('array', $data); + $this->assertEquals($expectedResult, $data); + } +} diff --git a/tests/LocalePathBuilderTest.php b/tests/LocalePathBuilderTest.php deleted file mode 100644 index c63613e..0000000 --- a/tests/LocalePathBuilderTest.php +++ /dev/null @@ -1,102 +0,0 @@ -basePath = __DIR__.'/data'; - $this->locator = new ResourceLocator($this->basePath); - - $this->locator->registerStream('locale'); - - // Add them one at a time to simulate how they are added in SprinkleManager - $this->locator->registerLocation('core'); - $this->locator->registerLocation('account'); - $this->locator->registerLocation('admin'); - } - - /** - * Test paths for a single locale. - */ - public function testOne() - { - // Arrange - $builder = new LocalePathBuilder($this->locator, 'locale://', 'en_US'); - - // Act - $paths = $builder->buildPaths(); - - // Assert - $this->assertEquals([ - $this->basePath.'/core/locale/en_US/readme.php', - $this->basePath.'/core/locale/en_US/test.php', - $this->basePath.'/core/locale/en_US/twig.php', - ], $paths); - } - - /** - * Test paths for multiple locales. - */ - public function testMany() - { - // Arrange - $builder = new LocalePathBuilder($this->locator, 'locale://', 'en_US'); - $builder->addLocales('fr_FR'); - - // Act - $paths = $builder->buildPaths(); - - // Assert - $this->assertEquals([ - $this->basePath.'/core/locale/en_US/readme.php', - $this->basePath.'/core/locale/en_US/test.php', - $this->basePath.'/core/locale/en_US/twig.php', - $this->basePath.'/core/locale/fr_FR/test.php', - ], $paths); - } - - /** - * Test locale values. - */ - public function testInitLocales() - { - // Arrange - $builder = new LocalePathBuilder($this->locator, 'locale://'); - $builder->addLocales('en_US'); - - // Act - $locales = $builder->getLocales(); - - // Assert - $this->assertEquals(['en_US'], $locales); - } - - public function testRepeatLocales() - { - // Arrange - $builder = new LocalePathBuilder($this->locator, 'locale://', ['en_US', 'fr_FR', 'fr_FR', 'en_US', 'fr_FR']); - $builder->addLocales(['en_US', 'en_US']); - - // Act - $locales = $builder->getLocales(); - - // Assert - $this->assertEquals(['fr_FR', 'en_US'], $locales); - } -} diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index eb3e31d..4e1ef5e 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -142,4 +142,10 @@ public function testGetDetails(Locale $locale) }*/ + + /* + TODO : + - fr_CA + - Null Parent + */ } diff --git a/tests/data/core/locale/es_ES/config.yaml b/tests/data/core/locale/es_ES/config.yaml index aa2dd17..2f07594 100644 --- a/tests/data/core/locale/es_ES/config.yaml +++ b/tests/data/core/locale/es_ES/config.yaml @@ -6,4 +6,3 @@ authors: options: plural: 1 parents: -- en_US diff --git a/tests/data/core/locale/es_ES/foo/bar.php b/tests/data/core/locale/es_ES/foo/bar.php new file mode 100644 index 0000000..4377087 --- /dev/null +++ b/tests/data/core/locale/es_ES/foo/bar.php @@ -0,0 +1,13 @@ + 'BAR', +]; From 38dbab4738b526c31e0c6f1e868e876549b2f425 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 21 Oct 2019 22:22:44 -0400 Subject: [PATCH 06/40] Saving progress in Dictionary --- src/Dictionary.php | 79 +++++++++++++----- src/DictionaryInterface.php | 2 +- src/Locale.php | 16 +++- src/LocaleInterface.php | 9 +- tests/DictionaryTest.php | 162 +++++++++++++++++++++++++++++++++--- 5 files changed, 234 insertions(+), 34 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index fb0dc7e..829e15c 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -11,8 +11,7 @@ namespace UserFrosting\I18n; use UserFrosting\Support\Repository\Loader\ArrayFileLoader; -use UserFrosting\I18n\LocaleInterface; -use UserFrosting\UniformResourceLocator\Resource; +use UserFrosting\Support\Repository\Loader\FileRepositoryLoader; use UserFrosting\UniformResourceLocator\ResourceLocatorInterface; /** @@ -24,6 +23,11 @@ */ class Dictionary implements DictionaryInterface { + /** + * @var string Base URI for locator + */ + protected $uri = 'locale://'; + /** * @var LocaleInterface */ @@ -34,19 +38,26 @@ class Dictionary implements DictionaryInterface */ protected $locator; + /** + * @var FileRepositoryLoader + */ + protected $fileLoader; + /** * @var array Locale "Key => translation" data matrix */ protected $dictionary = []; /** - * @param LocaleInterface $locale + * @param LocaleInterface $locale * @param ResourceLocatorInterface $locator + * @param FileRepositoryLoader $fileLoader File loader used to load each dictionnay files (default to Array Loader) */ - public function __construct(LocaleInterface $locale, ResourceLocatorInterface $locator) + public function __construct(LocaleInterface $locale, ResourceLocatorInterface $locator, FileRepositoryLoader $fileLoader = null) { $this->locale = $locale; $this->locator = $locator; + $this->fileLoader = is_null($fileLoader) ? new ArrayFileLoader([]) : $fileLoader; } /** @@ -65,23 +76,51 @@ public function getDictionary(): array } /** - * Load the dictionnary from file + * Set the locator base URI (default 'locale://'). + * + * @param string $uri + */ + public function setUri(string $uri): void + { + $this->uri = $uri; + } + + /** + * Return the file repository loader used to load * - * @return array The locale dictionnary + * @return FileRepositoryLoader + */ + public function getFileLoader(): FileRepositoryLoader + { + return $this->fileLoader; + } + + /** + * Load the dictionnary from file. + * + * @return (string|array)[] The locale dictionnary */ protected function loadDictionary(): array { // Get list of files to load $files = $this->getDictionaryFiles(); + // Stop if no files are present + if (empty($files)) { + return []; + } + // Load all files content - $loader = new ArrayFileLoader($files); + $loader = $this->getFileLoader(); + $loader->setPaths($files); + return $loader->load(); } /** - * Returns a list of files to load - * @return array[Resource] + * Returns a list of files to load. + * + * @return string[] */ protected function getDictionaryFiles(): array { @@ -91,8 +130,7 @@ protected function getDictionaryFiles(): array $parents = $this->locale->getDependentLocales(); if (!empty($parents)) { - foreach ($parents as $parent) - { + foreach ($parents as $parent) { $parentLocale = new Locale($parent, "locale://$parent/config.yaml"); //TODO : Recursively load dictionnary instead, cause a dependant can have dependencies $files = array_merge($files, $this->getFilesForLocale($parentLocale)); @@ -107,10 +145,10 @@ protected function getDictionaryFiles(): array } /** - * Remove config files from locator results + * Remove config files from locator results. * - * @param array $files - * @return array[Resource] + * @param \UserFrosting\UniformResourceLocator\ResourceInterface[] $files + * @return string[] */ protected function filterDictionaryFiles(array $files): array { @@ -119,8 +157,9 @@ protected function filterDictionaryFiles(array $files): array $filtered = []; foreach ($files as $file) { - if ($file->getExtension() == "php") { - $filtered[] = $file; + if ($file->getExtension() == 'php') { + // Add to filtered and convert Resource to string (path) + $filtered[] = (string) $file; } } @@ -128,13 +167,13 @@ protected function filterDictionaryFiles(array $files): array } /** - * List all files for a given locale using the locator + * List all files for a given locale using the locator. * - * @param LocaleInterface $locale - * @return array[Resource] + * @param LocaleInterface $locale + * @return \UserFrosting\UniformResourceLocator\ResourceInterface[] */ protected function getFilesForLocale(LocaleInterface $locale): array { - return $this->locator->listResources('locale://' . $locale->getIndentifier(), true); + return $this->locator->listResources($this->uri . $locale->getIndentifier(), true); } } diff --git a/src/DictionaryInterface.php b/src/DictionaryInterface.php index d3528f5..d239288 100644 --- a/src/DictionaryInterface.php +++ b/src/DictionaryInterface.php @@ -11,7 +11,7 @@ namespace UserFrosting\I18n; /** - * Locale Dictionnary + * Locale Dictionnary. * * Used to return all "Key => translation" data matrix * diff --git a/src/Locale.php b/src/Locale.php index 4b165b6..b31fe31 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -95,7 +95,7 @@ public function getIndentifier(): string /** * Return the raw configuration data. * - * @return array + * @return (array|string)[] */ public function getConfig(): array { @@ -122,6 +122,20 @@ public function getName(): string return $this->config['name']; } + /** + * Return the number representing the plural rule to use for this locale. + * + * @return int + */ + public function getPluralRule(): int + { + if (isset($this->config['options']['plural'])) { + return $this->config['options']['plural']; + } else { + return 1; + } + } + /** * Return the localized version of the locale name. * diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index 0976595..00c064f 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -49,7 +49,7 @@ public function getIndentifier(): string; /** * Return the raw configuration data. * - * @return array + * @return (array|string)[] */ public function getConfig(): array; @@ -67,6 +67,13 @@ public function getDependentLocales(): ?array; */ public function getName(): string; + /** + * Return the number representing the plural rule to use for this locale. + * + * @return int + */ + public function getPluralRule(): int; + /** * Return the localized version of the locale name. * diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index 8f90160..988b23d 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -15,7 +15,8 @@ use UserFrosting\I18n\Dictionary; use UserFrosting\I18n\DictionaryInterface; use UserFrosting\I18n\Locale; -use UserFrosting\I18n\LocaleInterface; +use UserFrosting\Support\Repository\Loader\ArrayFileLoader; +use UserFrosting\UniformResourceLocator\Resource; use UserFrosting\UniformResourceLocator\ResourceLocator; class DictionaryTest extends TestCase @@ -42,9 +43,8 @@ public function tearDown() Mockery::close(); } - public function testConstructor() + public function testConstructor(): void { - /** @var LocaleInterface */ $locale = Mockery::mock(Locale::class); $dictionary = new Dictionary($locale, $this->locator); @@ -54,21 +54,161 @@ public function testConstructor() /** * @depends testConstructor */ - /*public function testGetDictionary_withNoDependentLocaleNoData() + public function testGetDictionary_withNoDependentLocaleNoData(): void { + // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); - $dictionary = new Dictionary($locale, $this->locator); + // Prepare mock Locator - Return no file + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->with('locale://aa_bb', true)->andReturn([]); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldNotReceive('setPaths'); + $fileLoader->shouldNotReceive('load'); + + // Set expectations + $expectedResult = []; + + // Get dictionnary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /** + * @depends testGetDictionary_withNoDependentLocaleNoData + */ + public function testSetUri(): void + { + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare mock Locator - Return no file + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->with('foo://aa_bb', true)->andReturn([]); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldNotReceive('setPaths'); + $fileLoader->shouldNotReceive('load'); + + // Set expectations + $expectedResult = []; + + // Get dictionnary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $dictionary->setUri('foo://'); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /** + * @depends testGetDictionary_withNoDependentLocaleNoData + */ + public function testGetDictionary_withNoDependentLocaleWithData(): void + { + // Set expectations + $expectedResult = ['Foo' => 'Bar']; + + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare mock Resource - File `Foo/Bar/File1.php` + $file = Mockery::mock(Resource::class); + $file->shouldReceive('getExtension')->andReturn('php'); + $file->shouldReceive('__toString')->andReturn('Foo/Bar/File1.php'); + + // Prepare mock Locator - Return the file + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->with('locale://aa_bb', true)->andReturn([$file]); + + // Prepare mock FileLoader - Will return the mock file, with a mock data + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldReceive('setPaths')->with(['Foo/Bar/File1.php']); + $fileLoader->shouldReceive('load')->andReturn($expectedResult); - $this->assertInternalType('array', $dictionary->getDictionary()); - $this->assertEquals('array', $dictionary->getDictionary()); - }*/ + // Get dictionnary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /** + * @depends testGetDictionary_withNoDependentLocaleWithData + */ + public function testGetDictionary_withNoDependentLocaleWithManyFiles(): void + { + // Set expectations + $expectedResult = [ + 'Foo' => 'Bar', + 'Bar' => 'Foo', + 'test' => [ + 'Bar' => 'Rab', + 'Foo' => 'Oof', + ], + ]; + + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare first mock Resource - File `Foo/Bar/File1.php` + $file1 = Mockery::mock(Resource::class); + $file1->shouldReceive('getExtension')->andReturn('php'); + $file1->shouldReceive('__toString')->andReturn('Foo/Bar/File1.php'); + + // Prepare second mock Resource - File `Bar/Foo/File2.php` + $file2 = Mockery::mock(Resource::class); + $file2->shouldReceive('getExtension')->andReturn('php'); + $file2->shouldReceive('__toString')->andReturn('Bar/Foo/File2.php'); + + // Prepare Third mock Resource - non `.php` file + $file3 = Mockery::mock(Resource::class); + $file3->shouldReceive('getExtension')->andReturn('txt'); + $file3->shouldNotReceive('__toString'); + + // Prepare mock Locator - Return the file + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->with('locale://aa_bb', true)->andReturn([$file1, $file2, $file3]); + + // Prepare mock FileLoader - Will return the mock file, with a mock data + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldReceive('setPaths')->with(['Foo/Bar/File1.php', 'Bar/Foo/File2.php']); + $fileLoader->shouldReceive('load')->andReturn($expectedResult); + + // Get dictionnary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } /** + * Integration test with default + * * @depends testConstructor */ - public function testGetDictionary_withRealLocale() + public function testGetDictionary_withRealLocale(): void { $locale = new Locale('es_ES', 'locale://es_ES/config.yaml'); $dictionary = new Dictionary($locale, $this->locator); @@ -78,12 +218,12 @@ public function testGetDictionary_withRealLocale() 1 => '{{plural}} coche', 2 => '{{plural}} coches', ], - 'FOO' => 'BAR' + 'FOO' => 'BAR', ]; $data = $dictionary->getDictionary(); - $this->assertInternalType('array', $data); + $this->assertIsArray($data); $this->assertEquals($expectedResult, $data); } } From 77e50cbd71905f7c60e53609d4a38ffc32f9d211 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 21 Oct 2019 22:57:31 -0400 Subject: [PATCH 07/40] Improved `filterDictionaryFiles` --- src/Dictionary.php | 27 +++++++++++---------------- tests/DictionaryTest.php | 4 ++-- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index 829e15c..73193e4 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -15,7 +15,7 @@ use UserFrosting\UniformResourceLocator\ResourceLocatorInterface; /** - * Locale Dictionnary + * Locale Dictionnary. * * Load all locale all "Key => translation" data matrix * @@ -86,7 +86,7 @@ public function setUri(string $uri): void } /** - * Return the file repository loader used to load + * Return the file repository loader used to load. * * @return FileRepositoryLoader */ @@ -145,35 +145,30 @@ protected function getDictionaryFiles(): array } /** - * Remove config files from locator results. + * Remove config files from locator results and convert ResourceInterface to path/string + * + * @param \UserFrosting\UniformResourceLocator\ResourceInterface[] $files * - * @param \UserFrosting\UniformResourceLocator\ResourceInterface[] $files * @return string[] */ protected function filterDictionaryFiles(array $files): array { - //TODO : Use Array_filter - - $filtered = []; - - foreach ($files as $file) { + return array_filter($files, function ($file) { if ($file->getExtension() == 'php') { - // Add to filtered and convert Resource to string (path) - $filtered[] = (string) $file; + return (string) $file; } - } - - return $filtered; + }); } /** * List all files for a given locale using the locator. * - * @param LocaleInterface $locale + * @param LocaleInterface $locale + * * @return \UserFrosting\UniformResourceLocator\ResourceInterface[] */ protected function getFilesForLocale(LocaleInterface $locale): array { - return $this->locator->listResources($this->uri . $locale->getIndentifier(), true); + return $this->locator->listResources($this->uri.$locale->getIndentifier(), true); } } diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index 988b23d..d534c14 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -157,8 +157,8 @@ public function testGetDictionary_withNoDependentLocaleWithManyFiles(): void { // Set expectations $expectedResult = [ - 'Foo' => 'Bar', - 'Bar' => 'Foo', + 'Foo' => 'Bar', + 'Bar' => 'Foo', 'test' => [ 'Bar' => 'Rab', 'Foo' => 'Oof', From 57a13680b548e937ea1e18284d956ae06f5b64ed Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Wed, 23 Oct 2019 21:24:13 -0400 Subject: [PATCH 08/40] Changes to Locale: - Default yaml config now `locale.yaml` - getDependentLocales now return an array of locales - getDependentLocalesIndentifier added - yaml now optional - getDependentLocalesIndentifier can't return a null --- src/Locale.php | 33 +++++++-- src/LocaleInterface.php | 17 +++-- tests/LocaleTest.php | 68 ++++++++++++++++--- .../locale/fr_FR/{config.yaml => locale.yaml} | 0 .../locale/en_US/{config.yaml => locale.yaml} | 0 .../locale/es_ES/{config.yaml => locale.yaml} | 3 - .../locale/fr_FR/{config.yaml => locale.yaml} | 0 .../locale/fr_CA/{config.yaml => locale.yaml} | 0 8 files changed, 95 insertions(+), 26 deletions(-) rename tests/data/account/locale/fr_FR/{config.yaml => locale.yaml} (100%) rename tests/data/core/locale/en_US/{config.yaml => locale.yaml} (100%) rename tests/data/core/locale/es_ES/{config.yaml => locale.yaml} (71%) rename tests/data/core/locale/fr_FR/{config.yaml => locale.yaml} (100%) rename tests/data/fr_CA/locale/fr_CA/{config.yaml => locale.yaml} (100%) diff --git a/src/Locale.php b/src/Locale.php index b31fe31..9f020aa 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -40,12 +40,12 @@ class Locale implements LocaleInterface * Create locale class. * * @param string $identifier The locale identifier (ie. "en_US") - * @param string $configFile The path to the locale config file + * @param string|null $configFile The path to the locale config file */ - public function __construct(string $identifier, string $configFile) + public function __construct(string $identifier, ?string $configFile = null) { $this->identifier = $identifier; - $this->configFile = $configFile; + $this->configFile = (isset($configFile)) ? $configFile : "locale://$identifier/locale.yaml"; // Load locale config $this->loadConfig(); @@ -103,13 +103,32 @@ public function getConfig(): array } /** - * Return the raw configuration data. + * Return an array of parent locales + * + * @return Locale[] + */ + public function getDependentLocales(): array + { + $parents = $this->getDependentLocalesIdentifier(); + + // Transform locale identifier to locale instance + $locales = array_map(function($value) { + return new Locale($value); + }, $parents); + + return $locales; + } + + /** + * Return a list of parent locale identifier (eg. [fr_FR, en_US]) * - * @return array + * @return string[] */ - public function getDependentLocales(): ?array + public function getDependentLocalesIdentifier(): array { - return $this->config['parents']; + $parents = $this->config['parents']; + + return (is_array($parents)) ? $this->config['parents'] : []; } /** diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index 00c064f..ee92f99 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -21,9 +21,9 @@ interface LocaleInterface * Create locale class. * * @param string $identifier The locale identifier (ie. "en_US") - * @param string $configFile The path to the locale config file + * @param string|null $configFile The path to the locale config file */ - public function __construct(string $identifier, string $configFile); + public function __construct(string $identifier, ?string $configFile = null); /** * Returns the list of authors of the locale. @@ -54,11 +54,18 @@ public function getIndentifier(): string; public function getConfig(): array; /** - * Return the raw configuration data. + * Return an array of parent locales + * + * @return LocaleInterface[] + */ + public function getDependentLocales(): array; + + /** + * Return a list of parent locale identifier (eg. [fr_FR, en_US]) * - * @return array + * @return string[] */ - public function getDependentLocales(): ?array; + public function getDependentLocalesIdentifier(): array; /** * Return the name of the locale, in English form. diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index 4e1ef5e..e3d2dd8 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -37,7 +37,7 @@ public function setUp() public function testConstructor(): Locale { - $locale = new Locale('fr_FR', 'locale://fr_FR/config.yaml'); + $locale = new Locale('fr_FR', 'locale://fr_FR/locale.yaml'); $this->assertInstanceOf(LocaleInterface::class, $locale); return $locale; @@ -55,9 +55,19 @@ public function testConstructorWithNotFoundPath() public function testGetConfigFile(Locale $locale) { $data = $locale->getConfigFile(); - $this->assertInternalType('string', $data); + $this->assertIsString($data); - $this->assertSame('locale://fr_FR/config.yaml', $data); + $this->assertSame('locale://fr_FR/locale.yaml', $data); + } + + /** + * @depends testGetConfigFile + */ + public function testConstructorWithNotPath() + { + $locale = new Locale('fr_FR'); + $this->assertInstanceOf(LocaleInterface::class, $locale); + $this->assertSame('locale://fr_FR/locale.yaml', $locale->getConfigFile()); } /** @@ -66,7 +76,7 @@ public function testGetConfigFile(Locale $locale) public function testGetIndentifier(Locale $locale) { $data = $locale->getIndentifier(); - $this->assertInternalType('string', $data); + $this->assertIsString($data); $this->assertSame('fr_FR', $data); } @@ -77,7 +87,7 @@ public function testGetIndentifier(Locale $locale) public function testGetConfig(Locale $locale) { $data = $locale->getConfig(); - $this->assertInternalType('array', $data); + $this->assertIsArray($data); $this->assertSame([ 'name' => 'French', @@ -102,7 +112,7 @@ public function testGetConfig(Locale $locale) public function testGetAuthors(Locale $locale) { $data = $locale->getAuthors(); - $this->assertInternalType('array', $data); + $this->assertIsArray($data); $this->assertSame([ 'Foo Bar', @@ -119,16 +129,52 @@ public function testGetAuthors(Locale $locale) public function testGetDetails(Locale $locale) { //getName - $this->assertInternalType('string', $locale->getName()); + $this->assertIsString($locale->getName()); $this->assertSame('French', $locale->getName()); //getLocalizedName - $this->assertInternalType('string', $locale->getLocalizedName()); + $this->assertIsString($locale->getLocalizedName()); $this->assertSame('Français', $locale->getLocalizedName()); - //getDependentLocales - $this->assertInternalType('array', $locale->getDependentLocales()); - $this->assertSame(['en_US'], $locale->getDependentLocales()); + //getDependentLocalesIdentifier + $this->assertIsArray($locale->getDependentLocalesIdentifier()); + $this->assertSame(['en_US'], $locale->getDependentLocalesIdentifier()); + + //getPluralRule + $this->assertIsInt($locale->getPluralRule()); + $this->assertSame(2, $locale->getPluralRule()); + } + + /** + * @depends testConstructor + * @depends testGetConfig + */ + public function testGetPluralRule(Locale $locale) + { + $this->assertIsInt($locale->getPluralRule()); + $this->assertSame(2, $locale->getPluralRule()); + } + + /** + * @depends testConstructorWithNotPath + * @depends testGetPluralRule + */ + public function testGetPluralRuleWithNoRule() + { + $locale = new Locale('es_ES'); + $this->assertIsInt($locale->getPluralRule()); + $this->assertSame(1, $locale->getPluralRule()); + } + + /** + * @depends testConstructor + * @depends testGetDetails + */ + public function testGetDependentLocales(Locale $locale) + { + $result = $locale->getDependentLocales(); + $this->assertIsArray($result); + $this->assertInstanceOf(LocaleInterface::class, $result[0]); } /* diff --git a/tests/data/account/locale/fr_FR/config.yaml b/tests/data/account/locale/fr_FR/locale.yaml similarity index 100% rename from tests/data/account/locale/fr_FR/config.yaml rename to tests/data/account/locale/fr_FR/locale.yaml diff --git a/tests/data/core/locale/en_US/config.yaml b/tests/data/core/locale/en_US/locale.yaml similarity index 100% rename from tests/data/core/locale/en_US/config.yaml rename to tests/data/core/locale/en_US/locale.yaml diff --git a/tests/data/core/locale/es_ES/config.yaml b/tests/data/core/locale/es_ES/locale.yaml similarity index 71% rename from tests/data/core/locale/es_ES/config.yaml rename to tests/data/core/locale/es_ES/locale.yaml index 2f07594..aa56290 100644 --- a/tests/data/core/locale/es_ES/config.yaml +++ b/tests/data/core/locale/es_ES/locale.yaml @@ -3,6 +3,3 @@ localized_name: Español authors: - Salma Hayek - Selena Gomez -options: - plural: 1 -parents: diff --git a/tests/data/core/locale/fr_FR/config.yaml b/tests/data/core/locale/fr_FR/locale.yaml similarity index 100% rename from tests/data/core/locale/fr_FR/config.yaml rename to tests/data/core/locale/fr_FR/locale.yaml diff --git a/tests/data/fr_CA/locale/fr_CA/config.yaml b/tests/data/fr_CA/locale/fr_CA/locale.yaml similarity index 100% rename from tests/data/fr_CA/locale/fr_CA/config.yaml rename to tests/data/fr_CA/locale/fr_CA/locale.yaml From 3bd1f982b87bdaa1c2aa023313792cbd5e34e226 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Wed, 23 Oct 2019 22:02:50 -0400 Subject: [PATCH 09/40] Added dependent dictionnary load --- src/Dictionary.php | 55 +++++++++++++--------------------------- tests/DictionaryTest.php | 51 +++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index 73193e4..adffb0f 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -102,46 +102,27 @@ public function getFileLoader(): FileRepositoryLoader */ protected function loadDictionary(): array { - // Get list of files to load - $files = $this->getDictionaryFiles(); - - // Stop if no files are present - if (empty($files)) { - return []; - } - - // Load all files content - $loader = $this->getFileLoader(); - $loader->setPaths($files); + $dictionary = []; - return $loader->load(); - } - - /** - * Returns a list of files to load. - * - * @return string[] - */ - protected function getDictionaryFiles(): array - { - $files = []; + // Get list of files to load + $files = $this->getFiles(); + $files = $this->filterDictionaryFiles($files); - // First, load all parents locales - $parents = $this->locale->getDependentLocales(); + // Load all files content if files are present + if (!empty($files)) { + $loader = $this->getFileLoader(); + $loader->setPaths($files); - if (!empty($parents)) { - foreach ($parents as $parent) { - $parentLocale = new Locale($parent, "locale://$parent/config.yaml"); - //TODO : Recursively load dictionnary instead, cause a dependant can have dependencies - $files = array_merge($files, $this->getFilesForLocale($parentLocale)); - } + $dictionary = $loader->load(); } - // Now get for main locale - $files = array_merge($files, $this->getFilesForLocale($this->locale)); + // Now load dependent dictionnaries + foreach ($this->locale->getDependentLocales() as $locale) { + $dependentDictionary = new Dictionary($locale, $this->locator, $this->fileLoader); + $dictionary = array_merge_recursive($dependentDictionary->getDictionary(), $dictionary); + } - // Only keep .php files - return $this->filterDictionaryFiles($files); + return $dictionary; } /** @@ -163,12 +144,10 @@ protected function filterDictionaryFiles(array $files): array /** * List all files for a given locale using the locator. * - * @param LocaleInterface $locale - * * @return \UserFrosting\UniformResourceLocator\ResourceInterface[] */ - protected function getFilesForLocale(LocaleInterface $locale): array + protected function getFiles(): array { - return $this->locator->listResources($this->uri.$locale->getIndentifier(), true); + return $this->locator->listResources($this->uri.$this->locale->getIndentifier(), true); } } diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index d534c14..590d54c 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -204,13 +204,60 @@ public function testGetDictionary_withNoDependentLocaleWithManyFiles(): void } /** - * Integration test with default + * @depends testGetDictionary_withNoDependentLocaleNoData + */ + public function testGetDictionary_withDependentLocaleNoDataOnBoth(): void + { + // Prepare dependent mocked locale - ff_FF + $localeDependent = Mockery::mock(Locale::class); + $localeDependent->shouldReceive('getDependentLocales')->once()->andReturn([]); + $localeDependent->shouldReceive('getIndentifier')->once()->andReturn('ff_FF'); + + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->once()->andReturn([$localeDependent]); + $locale->shouldReceive('getIndentifier')->once()->andReturn('aa_bb'); + + // Prepare mock Locator - Return no file + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->once()->with('locale://aa_bb', true)->andReturn([]); + $locator->shouldReceive('listResources')->once()->with('locale://ff_FF', true)->andReturn([]); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldNotReceive('setPaths'); + $fileLoader->shouldNotReceive('load'); + + // Set expectations + $expectedResult = []; + + // Get dictionnary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /* + TODO : + - public function testGetDictionary_withDependentLocaleNoDataOnBoth(): void + - public function testGetDictionary_withDependentLocaleNoDataOnSecond(): void + - public function testGetDictionary_withDependentLocaleNoDataOnFirst(): void + - public function testGetDictionary_withDependentLocaleDataOnBoth(): void + - public function testGetDictionary_withManyDependentLocale(): void + - public function testGetDictionary_withRecursiveDependentLocale(): void + */ + + /** + * Integration test with default. * * @depends testConstructor */ public function testGetDictionary_withRealLocale(): void { - $locale = new Locale('es_ES', 'locale://es_ES/config.yaml'); + $locale = new Locale('es_ES'); $dictionary = new Dictionary($locale, $this->locator); $expectedResult = [ From bd56a7cdb611263633cc483b613c76c7f9626134 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Wed, 23 Oct 2019 22:05:30 -0400 Subject: [PATCH 10/40] Skip the constructor in interface --- src/LocaleInterface.php | 8 -------- tests/LocaleTest.php | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index ee92f99..7e5edd4 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -17,14 +17,6 @@ */ interface LocaleInterface { - /** - * Create locale class. - * - * @param string $identifier The locale identifier (ie. "en_US") - * @param string|null $configFile The path to the locale config file - */ - public function __construct(string $identifier, ?string $configFile = null); - /** * Returns the list of authors of the locale. * diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index e3d2dd8..61059e0 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -43,7 +43,7 @@ public function testConstructor(): Locale return $locale; } - public function testConstructorWithNotFoundPath() + public function testConstructorWithNotFoundPath(): void { $this->expectException(FileNotFoundException::class); $locale = new Locale('fr_FR', 'locale://fr_FR/dontexist.yaml'); @@ -52,7 +52,7 @@ public function testConstructorWithNotFoundPath() /** * @depends testConstructor */ - public function testGetConfigFile(Locale $locale) + public function testGetConfigFile(Locale $locale): void { $data = $locale->getConfigFile(); $this->assertIsString($data); @@ -63,7 +63,7 @@ public function testGetConfigFile(Locale $locale) /** * @depends testGetConfigFile */ - public function testConstructorWithNotPath() + public function testConstructorWithNotPath(): void { $locale = new Locale('fr_FR'); $this->assertInstanceOf(LocaleInterface::class, $locale); @@ -73,7 +73,7 @@ public function testConstructorWithNotPath() /** * @depends testConstructor */ - public function testGetIndentifier(Locale $locale) + public function testGetIndentifier(Locale $locale): void { $data = $locale->getIndentifier(); $this->assertIsString($data); @@ -84,7 +84,7 @@ public function testGetIndentifier(Locale $locale) /** * @depends testConstructor */ - public function testGetConfig(Locale $locale) + public function testGetConfig(Locale $locale): void { $data = $locale->getConfig(); $this->assertIsArray($data); @@ -109,7 +109,7 @@ public function testGetConfig(Locale $locale) * @depends testConstructor * @depends testGetConfig */ - public function testGetAuthors(Locale $locale) + public function testGetAuthors(Locale $locale): void { $data = $locale->getAuthors(); $this->assertIsArray($data); @@ -126,7 +126,7 @@ public function testGetAuthors(Locale $locale) * @depends testConstructor * @depends testGetConfig */ - public function testGetDetails(Locale $locale) + public function testGetDetails(Locale $locale): void { //getName $this->assertIsString($locale->getName()); @@ -149,7 +149,7 @@ public function testGetDetails(Locale $locale) * @depends testConstructor * @depends testGetConfig */ - public function testGetPluralRule(Locale $locale) + public function testGetPluralRule(Locale $locale): void { $this->assertIsInt($locale->getPluralRule()); $this->assertSame(2, $locale->getPluralRule()); @@ -159,7 +159,7 @@ public function testGetPluralRule(Locale $locale) * @depends testConstructorWithNotPath * @depends testGetPluralRule */ - public function testGetPluralRuleWithNoRule() + public function testGetPluralRuleWithNoRule(): void { $locale = new Locale('es_ES'); $this->assertIsInt($locale->getPluralRule()); @@ -170,7 +170,7 @@ public function testGetPluralRuleWithNoRule() * @depends testConstructor * @depends testGetDetails */ - public function testGetDependentLocales(Locale $locale) + public function testGetDependentLocales(Locale $locale): void { $result = $locale->getDependentLocales(); $this->assertIsArray($result); From 5208c965a3ba38dcf202fd54129fdf9befdb6c7d Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Wed, 23 Oct 2019 22:07:32 -0400 Subject: [PATCH 11/40] Changed `new X` to `new self` --- src/Dictionary.php | 4 ++-- src/Locale.php | 10 +++++----- src/LocaleInterface.php | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index adffb0f..7361936 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -118,7 +118,7 @@ protected function loadDictionary(): array // Now load dependent dictionnaries foreach ($this->locale->getDependentLocales() as $locale) { - $dependentDictionary = new Dictionary($locale, $this->locator, $this->fileLoader); + $dependentDictionary = new self($locale, $this->locator, $this->fileLoader); $dictionary = array_merge_recursive($dependentDictionary->getDictionary(), $dictionary); } @@ -126,7 +126,7 @@ protected function loadDictionary(): array } /** - * Remove config files from locator results and convert ResourceInterface to path/string + * Remove config files from locator results and convert ResourceInterface to path/string. * * @param \UserFrosting\UniformResourceLocator\ResourceInterface[] $files * diff --git a/src/Locale.php b/src/Locale.php index 9f020aa..11f85ac 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -39,7 +39,7 @@ class Locale implements LocaleInterface /** * Create locale class. * - * @param string $identifier The locale identifier (ie. "en_US") + * @param string $identifier The locale identifier (ie. "en_US") * @param string|null $configFile The path to the locale config file */ public function __construct(string $identifier, ?string $configFile = null) @@ -103,7 +103,7 @@ public function getConfig(): array } /** - * Return an array of parent locales + * Return an array of parent locales. * * @return Locale[] */ @@ -112,15 +112,15 @@ public function getDependentLocales(): array $parents = $this->getDependentLocalesIdentifier(); // Transform locale identifier to locale instance - $locales = array_map(function($value) { - return new Locale($value); + $locales = array_map(function ($value) { + return new self($value); }, $parents); return $locales; } /** - * Return a list of parent locale identifier (eg. [fr_FR, en_US]) + * Return a list of parent locale identifier (eg. [fr_FR, en_US]). * * @return string[] */ diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index 7e5edd4..d3b5351 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -46,14 +46,14 @@ public function getIndentifier(): string; public function getConfig(): array; /** - * Return an array of parent locales + * Return an array of parent locales. * * @return LocaleInterface[] */ public function getDependentLocales(): array; /** - * Return a list of parent locale identifier (eg. [fr_FR, en_US]) + * Return a list of parent locale identifier (eg. [fr_FR, en_US]). * * @return string[] */ From 90236114b5873d2f606f45691d4891c4bbde60b0 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Wed, 23 Oct 2019 22:09:54 -0400 Subject: [PATCH 12/40] Fix `getDependentLocalesIdentifier` --- src/Locale.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Locale.php b/src/Locale.php index 11f85ac..65a96b1 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -126,9 +126,7 @@ public function getDependentLocales(): array */ public function getDependentLocalesIdentifier(): array { - $parents = $this->config['parents']; - - return (is_array($parents)) ? $this->config['parents'] : []; + return (is_array($this->config['parents'])) ? $this->config['parents'] : []; } /** From 42b2695069ca4b63f24963982ace099b184d0d7a Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Thu, 24 Oct 2019 22:01:45 -0400 Subject: [PATCH 13/40] Added `getLocale` to Dictionary --- src/Dictionary.php | 12 +++++++++++- src/DictionaryInterface.php | 7 +++++++ tests/DictionaryTest.php | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index 7361936..d4803b7 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -44,7 +44,7 @@ class Dictionary implements DictionaryInterface protected $fileLoader; /** - * @var array Locale "Key => translation" data matrix + * @var array Locale "Key => translation" data matrix cache */ protected $dictionary = []; @@ -85,6 +85,16 @@ public function setUri(string $uri): void $this->uri = $uri; } + /** + * Return the associate locale + * + * @return LocaleInterface + */ + public function getLocale(): LocaleInterface + { + return $this->locale; + } + /** * Return the file repository loader used to load. * diff --git a/src/DictionaryInterface.php b/src/DictionaryInterface.php index d239288..e9b09ca 100644 --- a/src/DictionaryInterface.php +++ b/src/DictionaryInterface.php @@ -25,4 +25,11 @@ interface DictionaryInterface * @return string[] The locale dictionnary */ public function getDictionary(): array; + + /** + * Return the associate locale + * + * @return LocaleInterface + */ + public function getLocale(): LocaleInterface; } diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index 590d54c..af66b89 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -13,6 +13,7 @@ use Mockery; use PHPUnit\Framework\TestCase; use UserFrosting\I18n\Dictionary; +use UserFrosting\I18n\LocaleInterface; use UserFrosting\I18n\DictionaryInterface; use UserFrosting\I18n\Locale; use UserFrosting\Support\Repository\Loader\ArrayFileLoader; @@ -51,6 +52,20 @@ public function testConstructor(): void $this->assertInstanceOf(DictionaryInterface::class, $dictionary); } + /** + * @depends testConstructor + */ + public function testGetLocale(): void + { + $locale = Mockery::mock(Locale::class); + $locator = Mockery::mock(ResourceLocator::class); + $dictionary = new Dictionary($locale, $locator); //<-- Test no fileLoader too + + // Make sure constructor works + $this->assertInstanceOf(DictionaryInterface::class, $dictionary); + $this->assertInstanceOf(LocaleInterface::class, $dictionary->getLocale()); + } + /** * @depends testConstructor */ From 3c22e8b3bc89a56bccac5bcced21a6a29891e532 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sun, 27 Oct 2019 20:41:52 -0400 Subject: [PATCH 14/40] Finished DictionaryTest --- src/Dictionary.php | 25 ++- src/DictionaryInterface.php | 6 +- tests/DictionaryTest.php | 304 +++++++++++++++++++++++++++++++++--- 3 files changed, 308 insertions(+), 27 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index d4803b7..d91b73c 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -15,7 +15,7 @@ use UserFrosting\UniformResourceLocator\ResourceLocatorInterface; /** - * Locale Dictionnary. + * Locale Dictionary. * * Load all locale all "Key => translation" data matrix * @@ -64,7 +64,7 @@ public function __construct(LocaleInterface $locale, ResourceLocatorInterface $l * Returns all loaded locale Key => Translation data dictionary. * Won't load the whole thing twice if already loaded in the class. * - * @return string[] The locale dictionnary + * @return string[] The locale dictionary */ public function getDictionary(): array { @@ -86,7 +86,7 @@ public function setUri(string $uri): void } /** - * Return the associate locale + * Return the associate locale. * * @return LocaleInterface */ @@ -106,14 +106,17 @@ public function getFileLoader(): FileRepositoryLoader } /** - * Load the dictionnary from file. + * Load the dictionary from file. * - * @return (string|array)[] The locale dictionnary + * @return (string|array)[] The locale dictionary */ protected function loadDictionary(): array { $dictionary = []; + // List of loaded locales + $loadedLocale = [$this->locale->getIndentifier()]; + // Get list of files to load $files = $this->getFiles(); $files = $this->filterDictionaryFiles($files); @@ -128,8 +131,18 @@ protected function loadDictionary(): array // Now load dependent dictionnaries foreach ($this->locale->getDependentLocales() as $locale) { + + // Stop if locale already loaded to prevent recursion + $localesToLoad = array_merge([$locale->getIndentifier()], $locale->getDependentLocalesIdentifier()); + $intersection = array_intersect($localesToLoad, $loadedLocale); + if (!empty($intersection)) { + throw new \LogicException("Can't load dictionary. Dependencies recursion detected : " . implode(', ', $intersection)); + } + $dependentDictionary = new self($locale, $this->locator, $this->fileLoader); - $dictionary = array_merge_recursive($dependentDictionary->getDictionary(), $dictionary); + $dictionary = array_replace_recursive($dictionary, $dependentDictionary->getDictionary()); + + $loadedLocale[] = $locale->getIndentifier(); } return $dictionary; diff --git a/src/DictionaryInterface.php b/src/DictionaryInterface.php index e9b09ca..c4bc3de 100644 --- a/src/DictionaryInterface.php +++ b/src/DictionaryInterface.php @@ -11,7 +11,7 @@ namespace UserFrosting\I18n; /** - * Locale Dictionnary. + * Locale Dictionary. * * Used to return all "Key => translation" data matrix * @@ -22,12 +22,12 @@ interface DictionaryInterface /** * Returns all loaded locale Key => Translation data dictionary. * - * @return string[] The locale dictionnary + * @return string[] The locale dictionary */ public function getDictionary(): array; /** - * Return the associate locale + * Return the associate locale. * * @return LocaleInterface */ diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index af66b89..b853cd9 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -13,9 +13,9 @@ use Mockery; use PHPUnit\Framework\TestCase; use UserFrosting\I18n\Dictionary; -use UserFrosting\I18n\LocaleInterface; use UserFrosting\I18n\DictionaryInterface; use UserFrosting\I18n\Locale; +use UserFrosting\I18n\LocaleInterface; use UserFrosting\Support\Repository\Loader\ArrayFileLoader; use UserFrosting\UniformResourceLocator\Resource; use UserFrosting\UniformResourceLocator\ResourceLocator; @@ -74,6 +74,7 @@ public function testGetDictionary_withNoDependentLocaleNoData(): void // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); // Prepare mock Locator - Return no file @@ -88,7 +89,7 @@ public function testGetDictionary_withNoDependentLocaleNoData(): void // Set expectations $expectedResult = []; - // Get dictionnary + // Get dictionary $dictionary = new Dictionary($locale, $locator, $fileLoader); $data = $dictionary->getDictionary(); @@ -105,6 +106,7 @@ public function testSetUri(): void // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); // Prepare mock Locator - Return no file @@ -119,7 +121,7 @@ public function testSetUri(): void // Set expectations $expectedResult = []; - // Get dictionnary + // Get dictionary $dictionary = new Dictionary($locale, $locator, $fileLoader); $dictionary->setUri('foo://'); $data = $dictionary->getDictionary(); @@ -140,6 +142,7 @@ public function testGetDictionary_withNoDependentLocaleWithData(): void // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); // Prepare mock Resource - File `Foo/Bar/File1.php` @@ -156,7 +159,7 @@ public function testGetDictionary_withNoDependentLocaleWithData(): void $fileLoader->shouldReceive('setPaths')->with(['Foo/Bar/File1.php']); $fileLoader->shouldReceive('load')->andReturn($expectedResult); - // Get dictionnary + // Get dictionary $dictionary = new Dictionary($locale, $locator, $fileLoader); $data = $dictionary->getDictionary(); @@ -183,6 +186,7 @@ public function testGetDictionary_withNoDependentLocaleWithManyFiles(): void // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); // Prepare first mock Resource - File `Foo/Bar/File1.php` @@ -209,7 +213,7 @@ public function testGetDictionary_withNoDependentLocaleWithManyFiles(): void $fileLoader->shouldReceive('setPaths')->with(['Foo/Bar/File1.php', 'Bar/Foo/File2.php']); $fileLoader->shouldReceive('load')->andReturn($expectedResult); - // Get dictionnary + // Get dictionary $dictionary = new Dictionary($locale, $locator, $fileLoader); $data = $dictionary->getDictionary(); @@ -225,13 +229,15 @@ public function testGetDictionary_withDependentLocaleNoDataOnBoth(): void { // Prepare dependent mocked locale - ff_FF $localeDependent = Mockery::mock(Locale::class); - $localeDependent->shouldReceive('getDependentLocales')->once()->andReturn([]); - $localeDependent->shouldReceive('getIndentifier')->once()->andReturn('ff_FF'); + $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); + $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); + $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); - $locale->shouldReceive('getDependentLocales')->once()->andReturn([$localeDependent]); - $locale->shouldReceive('getIndentifier')->once()->andReturn('aa_bb'); + $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); // Prepare mock Locator - Return no file $locator = Mockery::mock(ResourceLocator::class); @@ -246,7 +252,107 @@ public function testGetDictionary_withDependentLocaleNoDataOnBoth(): void // Set expectations $expectedResult = []; - // Get dictionnary + // Get dictionary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /** + * @depends testGetDictionary_withDependentLocaleNoDataOnBoth + */ + public function testGetDictionary_withDependentLocaleAndDataOnAA(): void + { + // Set expectations + $expectedResult = [ + 'Foo' => 'Bar', + 'test' => [ + 'aaa' => 'AAA', + 'ccc' => '', + ], + ]; + + // Prepare dependent mocked locale - ff_FF + $localeDependent = Mockery::mock(Locale::class); + $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); + $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); + $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare first mock Resource - File `Foo/Bar/File1.php` + $file1 = Mockery::mock(Resource::class); + $file1->shouldReceive('getExtension')->andReturn('php'); + $file1->shouldReceive('__toString')->andReturn('Foo/Bar/File1.php'); + + // Prepare mock Locator - Return no file on ff_FF + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->once()->with('locale://aa_bb', true)->andReturn([$file1]); + $locator->shouldReceive('listResources')->once()->with('locale://ff_FF', true)->andReturn([]); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldReceive('setPaths')->with(['Foo/Bar/File1.php']); + $fileLoader->shouldReceive('load')->andReturn($expectedResult); + + // Get dictionary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /** + * @depends testGetDictionary_withDependentLocaleAndDataOnAA + */ + public function testGetDictionary_withDependentLocaleAndDataOnFF(): void + { + // Set expectations + $expectedResult = [ + 'Bar' => 'Foo', + 'test' => [ + 'bbb' => 'BBB', + 'ccc' => 'CCC', + ], + ]; + + // Prepare dependent mocked locale - ff_FF + $localeDependent = Mockery::mock(Locale::class); + $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); + $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); + $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare first mock Resource - File `Foo/Bar/File1.php` + $file1 = Mockery::mock(Resource::class); + $file1->shouldReceive('getExtension')->andReturn('php'); + $file1->shouldReceive('__toString')->andReturn('Bar/Foo/File2.php'); + + // Prepare mock Locator - Return no file on ff_FF + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->once()->with('locale://aa_bb', true)->andReturn([]); + $locator->shouldReceive('listResources')->once()->with('locale://ff_FF', true)->andReturn([$file1]); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldReceive('setPaths')->with(['Bar/Foo/File2.php']); + $fileLoader->shouldReceive('load')->andReturn($expectedResult); + + // Get dictionary $dictionary = new Dictionary($locale, $locator, $fileLoader); $data = $dictionary->getDictionary(); @@ -255,15 +361,177 @@ public function testGetDictionary_withDependentLocaleNoDataOnBoth(): void $this->assertEquals($expectedResult, $data); } - /* - TODO : - - public function testGetDictionary_withDependentLocaleNoDataOnBoth(): void - - public function testGetDictionary_withDependentLocaleNoDataOnSecond(): void - - public function testGetDictionary_withDependentLocaleNoDataOnFirst(): void - - public function testGetDictionary_withDependentLocaleDataOnBoth(): void - - public function testGetDictionary_withManyDependentLocale(): void - - public function testGetDictionary_withRecursiveDependentLocale(): void + /** + * @depends testGetDictionary_withDependentLocaleAndDataOnFF */ + public function testGetDictionary_withDependentLocaleDataOnBoth(): void + { + // Set expectations + $aa_AA_FILE = [ + 'Foo' => 'Bar', + 'test' => [ + 'aaa' => 'AAA', + 'ccc' => '', // Overwrites "CCC" + 'ddd' => 'DDD' // Overwrites "" + ], + ]; + $ff_FF_FILE = [ + 'Bar' => 'Foo', + 'test' => [ + 'bbb' => 'BBB', + 'ccc' => 'CCC', // Overwriten by "" + 'ddd' => '', //Overwriten by "DDD" + ], + ]; + + // NOTE : FF is a parent of AA. So FF should be loaded first + $expectedResult = [ + 'Foo' => 'Bar', + 'test' => [ + 'aaa' => 'AAA', + 'ccc' => '', + 'ddd' => 'DDD', + 'bbb' => 'BBB', + ], + 'Bar' => 'Foo', + ]; + + // Prepare dependent mocked locale - ff_FF + $localeDependent = Mockery::mock(Locale::class); + $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); + $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); + $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare first mock Resource - File `Foo/Bar/File1.php` + $file1 = Mockery::mock(Resource::class); + $file1->shouldReceive('getExtension')->andReturn('php'); + $file1->shouldReceive('__toString')->andReturn('Foo/Bar/File1.php'); + + // Prepare first mock Resource - File `Foo/Bar/File1.php` + $file2 = Mockery::mock(Resource::class); + $file2->shouldReceive('getExtension')->andReturn('php'); + $file2->shouldReceive('__toString')->andReturn('Bar/Foo/File2.php'); + + // Prepare mock Locator - Return no file on ff_FF + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->once()->with('locale://aa_bb', true)->andReturn([$file1]); + $locator->shouldReceive('listResources')->once()->with('locale://ff_FF', true)->andReturn([$file2]); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldReceive('setPaths')->once()->with(['Bar/Foo/File2.php']); + $fileLoader->shouldReceive('load')->once()->andReturn($ff_FF_FILE); + + $fileLoader->shouldReceive('setPaths')->once()->with(['Foo/Bar/File1.php']); + $fileLoader->shouldReceive('load')->once()->andReturn($aa_AA_FILE); + + // Get dictionary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /** + * @depends testGetDictionary_withDependentLocaleNoDataOnBoth + */ + public function testGetDictionary_withManyDependentLocale(): void + { + // Prepare dependent mocked locale - ee_EE + $localeSubDependent = Mockery::mock(Locale::class); + $localeSubDependent->shouldReceive('getDependentLocales')->andReturn([]); + $localeSubDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); + $localeSubDependent->shouldReceive('getIndentifier')->andReturn('ee_EE'); + + // Prepare dependent mocked locale - ff_FF + $localeDependent = Mockery::mock(Locale::class); + $localeDependent->shouldReceive('getDependentLocales')->andReturn([$localeSubDependent]); + $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ee_EE']); + $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + + // Prepare mocked locale - aa_bb + $locale = Mockery::mock(Locale::class); + $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare mock Locator - Return no file on ff_FF + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->once()->with('locale://aa_bb', true)->andReturn([]); + $locator->shouldReceive('listResources')->once()->with('locale://ff_FF', true)->andReturn([]); + $locator->shouldReceive('listResources')->once()->with('locale://ee_EE', true)->andReturn([]); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldNotReceive('setPaths'); + $fileLoader->shouldNotReceive('load'); + + // Get dictionary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals([], $data); + } + + /** + * @depends testGetDictionary_withManyDependentLocale + */ + public function testGetDictionary_withRecursiveDependentLocale(): void + { + // Set expectations + $aa_AA_FILE = [ + 'Foo' => 'Bar', + 'test' => [ + 'aaa' => 'AAA', + 'ccc' => '', // Overwrites "CCC" + 'ddd' => 'DDD' // Overwrites "" + ], + ]; + + // Prepare dependent mocked locale - ff_FF && aa_bb + $localeDependent = Mockery::mock(Locale::class); + $locale = Mockery::mock(Locale::class); + + $localeDependent->shouldReceive('getDependentLocales')->andReturn([$locale]); + $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn(['aa_bb']); + $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + + $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); + $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + + // Prepare first mock Resource - File `Foo/Bar/File1.php` + $file1 = Mockery::mock(Resource::class); + $file1->shouldReceive('getExtension')->andReturn('php'); + $file1->shouldReceive('__toString')->andReturn('Foo/Bar/File1.php'); + + // Prepare mock Locator - Return no file on ff_FF + $locator = Mockery::mock(ResourceLocator::class); + $locator->shouldReceive('listResources')->once()->with('locale://aa_bb', true)->andReturn([$file1]); + $locator->shouldReceive('listResources')->never()->with('locale://ff_FF', true); + + // Prepare mock FileLoader - No files, so loader shouldn't load anything + $fileLoader = Mockery::mock(ArrayFileLoader::class); + $fileLoader->shouldReceive('setPaths')->once()->with(['Foo/Bar/File1.php']); + $fileLoader->shouldReceive('load')->once()->andReturn($aa_AA_FILE); + + // Get dictionary + $dictionary = new Dictionary($locale, $locator, $fileLoader); + + // Expect exception + $this->expectException(\LogicException::class); + $data = $dictionary->getDictionary(); + } /** * Integration test with default. From e27cddc7e55a6bd78c0bd9e6f104b56cb600e887 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 29 Oct 2019 21:16:28 -0400 Subject: [PATCH 15/40] Reworked dictionary tests & data + Fix order of merge in Dictionary --- src/Dictionary.php | 4 +- tests/DictionaryTest.php | 115 +++++++++++----- tests/LocaleTest.php | 2 +- .../locale/en_US/locale.yaml | 2 - tests/data/dictionary/locale/en_US/test.php | 18 +++ .../locale/es_ES/foo/bar.php | 0 .../locale/es_ES/locale.yaml | 0 tests/data/dictionary/locale/es_ES/test.php | 15 +++ .../data/dictionary/locale/es_ES/zzz/bar.php | 13 ++ .../locale/fr_CA/locale.yaml | 0 tests/data/dictionary/locale/fr_CA/test.php | 13 ++ .../locale/fr_FR/locale.yaml | 0 tests/data/dictionary/locale/fr_FR/test.php | 18 +++ tests/data/fr_CA/locale/fr_CA/test.php | 126 ------------------ .../account/locale/fr_FR/locale.yaml | 9 ++ .../account/locale/fr_FR/test.php | 0 .../sprinkles/core/locale/en_US/locale.yaml | 6 + .../core/locale/en_US/readme.php | 0 .../core/locale/en_US/test.php | 0 .../core/locale/en_US/twig.php | 0 .../sprinkles/core/locale/es_ES/foo/bar.php | 13 ++ .../sprinkles/core/locale/es_ES/locale.yaml | 5 + .../core/locale/es_ES/test.php | 0 .../core/locale/fr_FR/locale.yaml | 0 .../core/locale/fr_FR/test.php | 0 .../sprinkles/fr_CA/locale/fr_CA/locale.yaml | 9 ++ .../sprinkles/fr_CA/locale/fr_CA/test.php | 13 ++ 27 files changed, 217 insertions(+), 164 deletions(-) rename tests/data/{core => dictionary}/locale/en_US/locale.yaml (83%) create mode 100644 tests/data/dictionary/locale/en_US/test.php rename tests/data/{core => dictionary}/locale/es_ES/foo/bar.php (100%) rename tests/data/{core => dictionary}/locale/es_ES/locale.yaml (100%) create mode 100644 tests/data/dictionary/locale/es_ES/test.php create mode 100644 tests/data/dictionary/locale/es_ES/zzz/bar.php rename tests/data/{fr_CA => dictionary}/locale/fr_CA/locale.yaml (100%) create mode 100644 tests/data/dictionary/locale/fr_CA/test.php rename tests/data/{account => dictionary}/locale/fr_FR/locale.yaml (100%) create mode 100644 tests/data/dictionary/locale/fr_FR/test.php delete mode 100644 tests/data/fr_CA/locale/fr_CA/test.php create mode 100644 tests/data/sprinkles/account/locale/fr_FR/locale.yaml rename tests/data/{ => sprinkles}/account/locale/fr_FR/test.php (100%) create mode 100644 tests/data/sprinkles/core/locale/en_US/locale.yaml rename tests/data/{ => sprinkles}/core/locale/en_US/readme.php (100%) rename tests/data/{ => sprinkles}/core/locale/en_US/test.php (100%) rename tests/data/{ => sprinkles}/core/locale/en_US/twig.php (100%) create mode 100644 tests/data/sprinkles/core/locale/es_ES/foo/bar.php create mode 100644 tests/data/sprinkles/core/locale/es_ES/locale.yaml rename tests/data/{ => sprinkles}/core/locale/es_ES/test.php (100%) rename tests/data/{ => sprinkles}/core/locale/fr_FR/locale.yaml (100%) rename tests/data/{ => sprinkles}/core/locale/fr_FR/test.php (100%) create mode 100644 tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml create mode 100644 tests/data/sprinkles/fr_CA/locale/fr_CA/test.php diff --git a/src/Dictionary.php b/src/Dictionary.php index d91b73c..b077388 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -136,11 +136,11 @@ protected function loadDictionary(): array $localesToLoad = array_merge([$locale->getIndentifier()], $locale->getDependentLocalesIdentifier()); $intersection = array_intersect($localesToLoad, $loadedLocale); if (!empty($intersection)) { - throw new \LogicException("Can't load dictionary. Dependencies recursion detected : " . implode(', ', $intersection)); + throw new \LogicException("Can't load dictionary. Dependencies recursion detected : ".implode(', ', $intersection)); } $dependentDictionary = new self($locale, $this->locator, $this->fileLoader); - $dictionary = array_replace_recursive($dictionary, $dependentDictionary->getDictionary()); + $dictionary = array_replace_recursive($dependentDictionary->getDictionary(), $dictionary); $loadedLocale[] = $locale->getIndentifier(); } diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index b853cd9..afeb872 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -28,15 +28,10 @@ class DictionaryTest extends TestCase public function setUp() { - $this->basePath = __DIR__.'/data'; + $this->basePath = __DIR__.'/data/dictionary'; $this->locator = new ResourceLocator($this->basePath); - $this->locator->registerStream('locale'); - - // Add them one at a time to simulate how they are added in SprinkleManager - $this->locator->registerLocation('core'); - $this->locator->registerLocation('account'); - $this->locator->registerLocation('admin'); + $this->locator->registerStream('locale', '', null, true); } public function tearDown() @@ -367,15 +362,15 @@ public function testGetDictionary_withDependentLocaleAndDataOnFF(): void public function testGetDictionary_withDependentLocaleDataOnBoth(): void { // Set expectations - $aa_AA_FILE = [ + $fr_FR_FILE = [ 'Foo' => 'Bar', 'test' => [ 'aaa' => 'AAA', 'ccc' => '', // Overwrites "CCC" - 'ddd' => 'DDD' // Overwrites "" + 'ddd' => 'DDD', // Overwrites "" ], ]; - $ff_FF_FILE = [ + $en_US_FILE = [ 'Bar' => 'Foo', 'test' => [ 'bbb' => 'BBB', @@ -396,40 +391,40 @@ public function testGetDictionary_withDependentLocaleDataOnBoth(): void 'Bar' => 'Foo', ]; - // Prepare dependent mocked locale - ff_FF + // Prepare dependent mocked locale - en_US $localeDependent = Mockery::mock(Locale::class); $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + $localeDependent->shouldReceive('getIndentifier')->andReturn('en_US'); - // Prepare mocked locale - aa_bb + // Prepare mocked locale - fr_FR $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); - $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['en_US']); + $locale->shouldReceive('getIndentifier')->andReturn('fr_FR'); // Prepare first mock Resource - File `Foo/Bar/File1.php` - $file1 = Mockery::mock(Resource::class); - $file1->shouldReceive('getExtension')->andReturn('php'); - $file1->shouldReceive('__toString')->andReturn('Foo/Bar/File1.php'); + $file_FR = Mockery::mock(Resource::class); + $file_FR->shouldReceive('getExtension')->andReturn('php'); + $file_FR->shouldReceive('__toString')->andReturn('Locale/fr_FR/file.php'); // Prepare first mock Resource - File `Foo/Bar/File1.php` - $file2 = Mockery::mock(Resource::class); - $file2->shouldReceive('getExtension')->andReturn('php'); - $file2->shouldReceive('__toString')->andReturn('Bar/Foo/File2.php'); + $file_EN = Mockery::mock(Resource::class); + $file_EN->shouldReceive('getExtension')->andReturn('php'); + $file_EN->shouldReceive('__toString')->andReturn('Locale/en_US/file.php'); // Prepare mock Locator - Return no file on ff_FF $locator = Mockery::mock(ResourceLocator::class); - $locator->shouldReceive('listResources')->once()->with('locale://aa_bb', true)->andReturn([$file1]); - $locator->shouldReceive('listResources')->once()->with('locale://ff_FF', true)->andReturn([$file2]); + $locator->shouldReceive('listResources')->once()->with('locale://fr_FR', true)->andReturn([$file_FR]); + $locator->shouldReceive('listResources')->once()->with('locale://en_US', true)->andReturn([$file_EN]); // Prepare mock FileLoader - No files, so loader shouldn't load anything $fileLoader = Mockery::mock(ArrayFileLoader::class); - $fileLoader->shouldReceive('setPaths')->once()->with(['Bar/Foo/File2.php']); - $fileLoader->shouldReceive('load')->once()->andReturn($ff_FF_FILE); + $fileLoader->shouldReceive('setPaths')->once()->with(['Locale/fr_FR/file.php']); + $fileLoader->shouldReceive('load')->once()->andReturn($fr_FR_FILE); - $fileLoader->shouldReceive('setPaths')->once()->with(['Foo/Bar/File1.php']); - $fileLoader->shouldReceive('load')->once()->andReturn($aa_AA_FILE); + $fileLoader->shouldReceive('setPaths')->once()->with(['Locale/en_US/file.php']); + $fileLoader->shouldReceive('load')->once()->andReturn($en_US_FILE); // Get dictionary $dictionary = new Dictionary($locale, $locator, $fileLoader); @@ -494,7 +489,7 @@ public function testGetDictionary_withRecursiveDependentLocale(): void 'test' => [ 'aaa' => 'AAA', 'ccc' => '', // Overwrites "CCC" - 'ddd' => 'DDD' // Overwrites "" + 'ddd' => 'DDD', // Overwrites "" ], ]; @@ -536,7 +531,7 @@ public function testGetDictionary_withRecursiveDependentLocale(): void /** * Integration test with default. * - * @depends testConstructor + * @ depends testConstructor */ public function testGetDictionary_withRealLocale(): void { @@ -544,15 +539,69 @@ public function testGetDictionary_withRealLocale(): void $dictionary = new Dictionary($locale, $this->locator); $expectedResult = [ - 'X_CARS' => [ - 1 => '{{plural}} coche', - 2 => '{{plural}} coches', + 'FOO' => 'Foo', // bar/bar.php file will be loaded first + 'CAR' => 'Coche', + 'BAR' => 'FooFoo', // ...but zzz/bar.php will be loaded LAST because of alphabetical order ! + ]; + + $data = $dictionary->getDictionary(); + + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + /** + * @ depends testConstructor + */ + public function testGetDictionary_withRealLocale_withDependentLocaleDataOnBoth(): void + { + // Set expectations + // fr_FR depends on en_US. So FR data will be loaded over EN data + // Replicate testGetDictionary_withDependentLocaleDataOnBoth result + $expectedResult = [ + 'Foo' => 'Bar', + 'test' => [ + 'aaa' => 'AAA', + 'ccc' => '', + 'ddd' => 'DDD', + 'bbb' => 'BBB', + ], + 'Bar' => 'Foo', + ]; + + // Get dictionary + $locale = new Locale('fr_FR'); + $dictionary = new Dictionary($locale, $this->locator); + $data = $dictionary->getDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + + public function testGetDictionary_withRealLocale_withThirdDependentLocale(): void + { + // Set expectations + // fr_CA depends on fr_FR which depends on en_US. + // So CA data will be loaded over FR data will be loaded over EN data + // 'foo' key will be different + $expectedResult = [ + 'Foo' => 'Tabarnak', + 'test' => [ + 'aaa' => 'AAA', + 'ccc' => '', + 'ddd' => 'DDD', + 'bbb' => 'BBB', ], - 'FOO' => 'BAR', + 'Bar' => 'Foo', ]; + // Get dictionary + $locale = new Locale('fr_CA'); + $dictionary = new Dictionary($locale, $this->locator); $data = $dictionary->getDictionary(); + // Perform assertions $this->assertIsArray($data); $this->assertEquals($expectedResult, $data); } diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index 61059e0..61a4c88 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -24,7 +24,7 @@ class LocaleTest extends TestCase public function setUp() { - $this->basePath = __DIR__.'/data'; + $this->basePath = __DIR__.'/data/sprinkles'; $this->locator = new ResourceLocator($this->basePath); $this->locator->registerStream('locale'); diff --git a/tests/data/core/locale/en_US/locale.yaml b/tests/data/dictionary/locale/en_US/locale.yaml similarity index 83% rename from tests/data/core/locale/en_US/locale.yaml rename to tests/data/dictionary/locale/en_US/locale.yaml index 09f9d63..95d02de 100644 --- a/tests/data/core/locale/en_US/locale.yaml +++ b/tests/data/dictionary/locale/en_US/locale.yaml @@ -4,5 +4,3 @@ authors: - John Appleseed options: plural: 1 -parents: -- en_US diff --git a/tests/data/dictionary/locale/en_US/test.php b/tests/data/dictionary/locale/en_US/test.php new file mode 100644 index 0000000..a0a7628 --- /dev/null +++ b/tests/data/dictionary/locale/en_US/test.php @@ -0,0 +1,18 @@ + 'Foo', + 'test' => [ + 'bbb' => 'BBB', + 'ccc' => 'CCC', // Overwriten by "" + 'ddd' => '', //Overwriten by "DDD" + ], +]; diff --git a/tests/data/core/locale/es_ES/foo/bar.php b/tests/data/dictionary/locale/es_ES/foo/bar.php similarity index 100% rename from tests/data/core/locale/es_ES/foo/bar.php rename to tests/data/dictionary/locale/es_ES/foo/bar.php diff --git a/tests/data/core/locale/es_ES/locale.yaml b/tests/data/dictionary/locale/es_ES/locale.yaml similarity index 100% rename from tests/data/core/locale/es_ES/locale.yaml rename to tests/data/dictionary/locale/es_ES/locale.yaml diff --git a/tests/data/dictionary/locale/es_ES/test.php b/tests/data/dictionary/locale/es_ES/test.php new file mode 100644 index 0000000..6b89193 --- /dev/null +++ b/tests/data/dictionary/locale/es_ES/test.php @@ -0,0 +1,15 @@ + 'Foo', + 'BAR' => 'Bar', + 'CAR' => 'Coche', +]; diff --git a/tests/data/dictionary/locale/es_ES/zzz/bar.php b/tests/data/dictionary/locale/es_ES/zzz/bar.php new file mode 100644 index 0000000..7ad526a --- /dev/null +++ b/tests/data/dictionary/locale/es_ES/zzz/bar.php @@ -0,0 +1,13 @@ + 'FooFoo', +]; diff --git a/tests/data/fr_CA/locale/fr_CA/locale.yaml b/tests/data/dictionary/locale/fr_CA/locale.yaml similarity index 100% rename from tests/data/fr_CA/locale/fr_CA/locale.yaml rename to tests/data/dictionary/locale/fr_CA/locale.yaml diff --git a/tests/data/dictionary/locale/fr_CA/test.php b/tests/data/dictionary/locale/fr_CA/test.php new file mode 100644 index 0000000..e4a1828 --- /dev/null +++ b/tests/data/dictionary/locale/fr_CA/test.php @@ -0,0 +1,13 @@ + 'Tabarnak', +]; diff --git a/tests/data/account/locale/fr_FR/locale.yaml b/tests/data/dictionary/locale/fr_FR/locale.yaml similarity index 100% rename from tests/data/account/locale/fr_FR/locale.yaml rename to tests/data/dictionary/locale/fr_FR/locale.yaml diff --git a/tests/data/dictionary/locale/fr_FR/test.php b/tests/data/dictionary/locale/fr_FR/test.php new file mode 100644 index 0000000..268416c --- /dev/null +++ b/tests/data/dictionary/locale/fr_FR/test.php @@ -0,0 +1,18 @@ + 'Bar', + 'test' => [ + 'aaa' => 'AAA', + 'ccc' => '', // Overwrites "CCC" + 'ddd' => 'DDD', // Overwrites "" + ], +]; diff --git a/tests/data/fr_CA/locale/fr_CA/test.php b/tests/data/fr_CA/locale/fr_CA/test.php deleted file mode 100644 index 0e7c591..0000000 --- a/tests/data/fr_CA/locale/fr_CA/test.php +++ /dev/null @@ -1,126 +0,0 @@ - 2, //Required to get the right rule. French is 2, english is 1 - - 'USERNAME' => 'Nom d\'utilisateur', //Note the espace `\` caracter here. Won't be displayed in the test - - //"BASE_FALLBACK" => "Langue de secours", //We want to test if the english string will be displayed here - - 'ACCOUNT' => [ - '@TRANSLATION' => "Compte de l'utilisateur", //Don't need to escape if using double quote `"` - 'ALT' => 'Profil', - ], - - // Colors - 'COLOR' => [ - //Substrings - 'BLACK' => 'noir', - 'RED' => 'rouge', - 'WHITE' => 'blanc', - - //Plurals - 0 => 'couleur', - 1 => 'couleur', - 2 => 'couleurs', - ], - - // Cars - 'CAR' => [ - //Plurals - 1 => 'voiture', - 2 => 'voitures', - - //Substrings - 'GAS' => 'à essence', - 'EV' => [ - //"@TRANSLATION" => "électrique", //Can't work for french ! - // But since French is loaded on top to English and English have this one defined - // it will return the english string if we want to translate "EV" without a plural value. - // So we need to get rid of the English string : - '@TRANSLATION' => null, - - //We will pluralize instead - 1 => 'électrique', - 2 => 'électriques', - - //Sub-Substring - 'FULL' => '100% électrique', - 'HYBRID' => 'hybride', - 'PLUGIN_HYBRID' => 'hybride branchable', - ], - 'HYDROGEN' => "à l'hydrogène", - ], - 'X_CARS' => [ - 0 => 'aucune voiture', - 1 => 'une voiture', - 2 => '{{plural}} voitures', - ], - - // Placeholder strings - 'MY_CAR_STRING' => 'Je conduit une {{my_car}} de couleur {{color}}', - 'MY_CAR_MAKE' => 'Ma voiture est une {{car_make}}', - 'MY_CAR_YEAR' => "J'ai acheté ma voiture en {{year}}", - 'MY_CARS' => "J'ai {{x_cars}}", - - // Plural with placeholder - 'MY_EV_CARS' => [ - '@TRANSLATION' => 'Mes voitures électriques', - 1 => 'Le chat a une {{&CAR}} {{type}}', - 2 => 'Le chat a {{plural}} {{&CAR}} {{type}}', - ], - - // Custom plural key with no "zero" case. - // In english, "2" should be used when the plural value is zero. In french, "1" should be used - 'X_HUNGRY_CATS' => [ - '@PLURAL' => 'num', - 1 => '{{num}} chat affamé', - 2 => '{{num}} chats affamés', - ], - - // Min/max placeholder where the - 'TEST_LIMIT' => 'Votre test doit être entre {{min}} et {{max}} patates.', - 'MIN' => 'minimum', - //"MAX" => "maximum" //Leave disabled for tests - - // Empty array - 'EMPTY' => [ - - ], - - // Missing one rule - 'X_RULES' => [ - 0 => 'aucune règle', - 1 => '{{plural}} règle', - //2 => '{{plural}} règles', //Leave disabled for tests - ], - - // Missing all rules - 'X_BANANAS' => [ - 0 => 'aucune banane', - //1 => '{{plural}} banane', //Leave disabled for tests - //2 => '{{plural}} bananes', //Leave disabled for tests - ], - - // No rules are followed - 'X_DOGS' => [ - 5 => 'cinq chiens', - 101 => '101 Dalmatiens', - '1000' => 'Une tempête de chiens', - ], - - // keys as strings - 'X_TABLES' => [ - '0' => 'aucune table', - '1' => 'une table', - '2' => '{{plural}} tables', - ], -]; diff --git a/tests/data/sprinkles/account/locale/fr_FR/locale.yaml b/tests/data/sprinkles/account/locale/fr_FR/locale.yaml new file mode 100644 index 0000000..4606d22 --- /dev/null +++ b/tests/data/sprinkles/account/locale/fr_FR/locale.yaml @@ -0,0 +1,9 @@ +name: French +localized_name: Français +authors: + - Foo Bar + - Bar Foo +options: + plural: 2 +parents: + - en_US diff --git a/tests/data/account/locale/fr_FR/test.php b/tests/data/sprinkles/account/locale/fr_FR/test.php similarity index 100% rename from tests/data/account/locale/fr_FR/test.php rename to tests/data/sprinkles/account/locale/fr_FR/test.php diff --git a/tests/data/sprinkles/core/locale/en_US/locale.yaml b/tests/data/sprinkles/core/locale/en_US/locale.yaml new file mode 100644 index 0000000..95d02de --- /dev/null +++ b/tests/data/sprinkles/core/locale/en_US/locale.yaml @@ -0,0 +1,6 @@ +name: English +localized_name: English +authors: + - John Appleseed +options: + plural: 1 diff --git a/tests/data/core/locale/en_US/readme.php b/tests/data/sprinkles/core/locale/en_US/readme.php similarity index 100% rename from tests/data/core/locale/en_US/readme.php rename to tests/data/sprinkles/core/locale/en_US/readme.php diff --git a/tests/data/core/locale/en_US/test.php b/tests/data/sprinkles/core/locale/en_US/test.php similarity index 100% rename from tests/data/core/locale/en_US/test.php rename to tests/data/sprinkles/core/locale/en_US/test.php diff --git a/tests/data/core/locale/en_US/twig.php b/tests/data/sprinkles/core/locale/en_US/twig.php similarity index 100% rename from tests/data/core/locale/en_US/twig.php rename to tests/data/sprinkles/core/locale/en_US/twig.php diff --git a/tests/data/sprinkles/core/locale/es_ES/foo/bar.php b/tests/data/sprinkles/core/locale/es_ES/foo/bar.php new file mode 100644 index 0000000..4377087 --- /dev/null +++ b/tests/data/sprinkles/core/locale/es_ES/foo/bar.php @@ -0,0 +1,13 @@ + 'BAR', +]; diff --git a/tests/data/sprinkles/core/locale/es_ES/locale.yaml b/tests/data/sprinkles/core/locale/es_ES/locale.yaml new file mode 100644 index 0000000..aa56290 --- /dev/null +++ b/tests/data/sprinkles/core/locale/es_ES/locale.yaml @@ -0,0 +1,5 @@ +name: Spanish +localized_name: Español +authors: + - Salma Hayek + - Selena Gomez diff --git a/tests/data/core/locale/es_ES/test.php b/tests/data/sprinkles/core/locale/es_ES/test.php similarity index 100% rename from tests/data/core/locale/es_ES/test.php rename to tests/data/sprinkles/core/locale/es_ES/test.php diff --git a/tests/data/core/locale/fr_FR/locale.yaml b/tests/data/sprinkles/core/locale/fr_FR/locale.yaml similarity index 100% rename from tests/data/core/locale/fr_FR/locale.yaml rename to tests/data/sprinkles/core/locale/fr_FR/locale.yaml diff --git a/tests/data/core/locale/fr_FR/test.php b/tests/data/sprinkles/core/locale/fr_FR/test.php similarity index 100% rename from tests/data/core/locale/fr_FR/test.php rename to tests/data/sprinkles/core/locale/fr_FR/test.php diff --git a/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml new file mode 100644 index 0000000..c7cf5bf --- /dev/null +++ b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml @@ -0,0 +1,9 @@ +name: French Canadian +localized_name: Français Canadien +authors: + - Foo Bar + - Bar Foo +options: + plural: 2 +parents: + - fr_FR diff --git a/tests/data/sprinkles/fr_CA/locale/fr_CA/test.php b/tests/data/sprinkles/fr_CA/locale/fr_CA/test.php new file mode 100644 index 0000000..49b003a --- /dev/null +++ b/tests/data/sprinkles/fr_CA/locale/fr_CA/test.php @@ -0,0 +1,13 @@ + 'Nom d\'utilisateur tabarnak', //Note the espace `\` caracter here. Won't be displayed in the test +]; From e8e09241a6292be0bed6c5c6165f09e8de12c0ce Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 29 Oct 2019 21:41:51 -0400 Subject: [PATCH 16/40] Adapted Translator to new dictionary --- src/{MessageTranslator.php => Translator.php} | 35 ++-- ...eTranslatorTest.php => TranslatorTest.php} | 161 ++++++++++++------ 2 files changed, 125 insertions(+), 71 deletions(-) rename src/{MessageTranslator.php => Translator.php} (93%) rename tests/{MessageTranslatorTest.php => TranslatorTest.php} (75%) diff --git a/src/MessageTranslator.php b/src/Translator.php similarity index 93% rename from src/MessageTranslator.php rename to src/Translator.php index 9c5b39e..5dfc79c 100644 --- a/src/MessageTranslator.php +++ b/src/Translator.php @@ -10,20 +10,22 @@ namespace UserFrosting\I18n; +use Twig_Environment; +use Twig_Loader_Filesystem; use UserFrosting\Support\Repository\Repository; /** - * MessageTranslator Class. + * Translator Class. * * Translate message ids to a message in a specified language. * * @author Louis Charette * @author Alexander Weissman (https://alexanderweissman.com) */ -class MessageTranslator extends Repository +class Translator extends Repository { /** - * @var \Twig_Environment A Twig environment used to replace placeholders. + * @var Twig_Environment A Twig environment used to replace placeholders. */ protected $twig; @@ -32,17 +34,24 @@ class MessageTranslator extends Repository */ protected $defaultPluralKey = 'plural'; + /** + * @var DictionaryInterface + */ + protected $dictionary; + /** * Create the translator. * - * @param array $items + * @param DictionaryInterface $dictionary */ - public function __construct(array $items = []) + public function __construct(DictionaryInterface $dictionary) { - $this->items = $items; + $this->dictionary = $dictionary; + $this->items = $dictionary->getDictionary(); - $loader = new \Twig_Loader_Filesystem(); - $this->twig = new \Twig_Environment($loader); + // Preapre Twig Environment + $loader = new Twig_Loader_Filesystem(); + $this->twig = new Twig_Environment($loader); } /** @@ -297,20 +306,16 @@ public function getPluralForm($number, $forceRule = false) /** * Return the correct rule number to use. * - * @param bool|int $forceRule Force to use a particular rule. Otherwise, use the language defined one + * @param int|bool $forceRule Force to use a particular rule. Otherwise, use the language defined one * * @return int */ - protected function getPluralRuleNumber($forceRule) + protected function getPluralRuleNumber($forceRule): int { if ($forceRule !== false) { return $forceRule; } - if ($this->has('@PLURAL_RULE')) { - return $this->get('@PLURAL_RULE'); - } - - return 1; + return $this->dictionary->getLocale()->getPluralRule(); } } diff --git a/tests/MessageTranslatorTest.php b/tests/TranslatorTest.php similarity index 75% rename from tests/MessageTranslatorTest.php rename to tests/TranslatorTest.php index 30a9b3f..b47762c 100644 --- a/tests/MessageTranslatorTest.php +++ b/tests/TranslatorTest.php @@ -8,16 +8,18 @@ * @license https://github.com/userfrosting/i18n/blob/master/LICENSE.md (MIT License) */ -namespace UserFrosting\I18n; +namespace UserFrosting\I18n\Tests; use PHPUnit\Framework\TestCase; -use UserFrosting\Support\Repository\Loader\ArrayFileLoader; +use UserFrosting\I18n\Dictionary; +use UserFrosting\I18n\Locale; +use UserFrosting\I18n\Translator; use UserFrosting\UniformResourceLocator\ResourceLocator; -class MessageTranslatorTest extends TestCase +class TranslatorTest extends TestCase { /** @var string Test locale file location */ - protected $basePath = __DIR__.'/data'; + protected $basePath = __DIR__.'/data/sprinkles'; /** @var ResourceLocator */ protected $locator; @@ -32,30 +34,102 @@ public function setUp() // Add them one at a time to simulate how they are added in SprinkleManager $this->locator->registerLocation('core'); $this->locator->registerLocation('account'); - $this->locator->registerLocation('admin'); + $this->locator->registerLocation('admin'); // Simulate non existing sprinkle + $this->locator->registerLocation('fr_CA'); // Simulate the fr_CA locale ! + } + + /** + * Test paths for a single locale. + */ + /*public function testGetAvailableLocales() + { + $translator = new Translator($this->locator); + + $locales = $translator->getAvailableLocales(); + + // Assert + $this->assertEquals([ + 'en_US', + 'fr_CA', + 'fr_FR', + ], $locales); + }*/ + + /** + * Test locale with a plural option + */ + public function testGetPluralForm(): void + { + $translator = $this->getTranslator('en_US'); + $this->assertSame(1, $translator->getPluralForm(1)); + $this->assertSame(2, $translator->getPluralForm(2)); + $this->assertSame(2, $translator->getPluralForm(20)); + + // Test with 0. If `@PLURAL_RULE` 1 is applied, it will return `X_CARS.2` (zero is plural) + // With `@PLURAL_RULE` 0, it would have been `X_CARS.1` (no plurals) + // and with `@PLURAL_RULE` 2, would have been `X_CARS.1` also (0 is singular) + $this->assertEquals($translator->translate('X_CARS', 0), 'no cars'); + $this->assertEquals($translator->translate('X_CARS', 1), 'a car'); + $this->assertEquals($translator->translate('X_CARS', 2), '2 cars'); + } + + /** + * @depends testGetPluralForm + * Test locale wihtout a `@PLURAL_RULE`. + */ + public function testGetPluralFormWithNoDefineRule(): void + { + $translator = $this->getTranslator('es_ES'); + $this->assertSame(1, $translator->getPluralForm(1)); + $this->assertSame(2, $translator->getPluralForm(2)); + $this->assertSame(2, $translator->getPluralForm(20)); + + // Test with 0. If `@PLURAL_RULE` 1 is applied, it will return `X_CARS.2` (zero is plural) + // With `@PLURAL_RULE` 0, it would have been `X_CARS.1` (no plurals) + // and with `@PLURAL_RULE` 2, would have been `X_CARS.1` also (0 is singular) + $this->assertEquals($translator->translate('X_CARS', 0), '0 coches'); + } + + /** + * @depends testGetPluralForm + */ + public function testGetPluralFormWithException(): void + { + $translator = $this->getTranslator(); + $this->expectException(\OutOfRangeException::class); + $translator->getPluralForm(1, 132); } /** * @dataProvider localeStringProvider * - * @param string $key - * @param array $placeholders - * @param string $expectedResultEnglish - * @param string $expectedResultFrench + * @param string $key + * @param string[]|int $placeholders + * @param string $expectedResultEnglish + * @param string $expectedResultFrench */ - public function testTranslate($key, $placeholders, $expectedResultEnglish, $expectedResultFrench) + public function testTranslate(string $key, $placeholders, string $expectedResultEnglish, string $expectedResultFrench): void { $translator = $this->getTranslator(); - $this->assertEquals($translator->translate($key, $placeholders), $expectedResultEnglish); + $this->assertEquals($expectedResultEnglish, $translator->translate($key, $placeholders)); + + $frenchTranslator = $this->getTranslator('fr_FR'); + $this->assertEquals($expectedResultFrench, $frenchTranslator->translate($key, $placeholders)); + } - $frenchTranslator = $this->getTranslator(['en_US', 'fr_FR']); - $this->assertEquals($frenchTranslator->translate($key, $placeholders), $expectedResultFrench); + /** + * Basic test to see if triple dependency works + */ + public function testTranslateWithNestedDependencies(): void + { + $frenchTranslator = $this->getTranslator('fr_CA'); + $this->assertEquals($frenchTranslator->translate('USERNAME'), 'Nom d\'utilisateur tabarnak'); } /** * Run more complex translations outside the provider. */ - public function testTranslate_withNested() + public function testTranslate_withNested(): void { // English translator $translator = $this->getTranslator(); @@ -71,7 +145,7 @@ public function testTranslate_withNested() ]), 'I drive a red plug-in hybrid'); // FRENCH version - $frenchTranslator = $this->getTranslator(['en_US', 'fr_FR']); + $frenchTranslator = $this->getTranslator('fr_FR'); // Example of a lang key in a placeholder // N.B.: In a real life situation, it's recommended to create a new Top level plural instead @@ -87,9 +161,9 @@ public function testTranslate_withNested() /** * DataProvider for testTranslateEN. * - * @return array [$key, $placeholders, $expectedResultEnglish, $expectedResultFrench] + * @return mixed[] [$key, $placeholders, $expectedResultEnglish, $expectedResultFrench] */ - public function localeStringProvider() + public function localeStringProvider(): array { return [ // Test most basic functionality @@ -202,7 +276,7 @@ public function localeStringProvider() /** * Test the readme examples. */ - public function testReadme() + public function testReadme(): void { // Create the $translator object $translator = $this->getTranslator(); @@ -234,7 +308,7 @@ public function testReadme() /** * Test for placeholder applied to `$key` if it doesn't match any languages keys. */ - public function testWithoutKeys() + public function testWithoutKeys(): void { $translator = $this->getTranslator(); $this->assertEquals($translator->translate('You are {{status}}', ['status' => 'dumb']), 'You are dumb'); @@ -243,45 +317,22 @@ public function testWithoutKeys() /** * @dataProvider twigProvider * - * @param string $key - * @param array $placeholders - * @param string $expectedResult + * @param string $key + * @param string[]|int $placeholders + * @param string $expectedResult */ - public function testTwigFilters($key, $placeholders, $expectedResult) + public function testTwigFilters(string $key, $placeholders, string $expectedResult): void { $translator = $this->getTranslator(); $this->assertEquals($translator->translate($key, $placeholders), $expectedResult); } - /** - * @expectedException \OutOfRangeException - */ - public function testGetPluralFormWithException() - { - $this->getTranslator()->getPluralForm(1, 132); - } - - /** - * Test locale wihtout a `@PLURAL_RULE`. - */ - public function testGetPluralFormWithNoDefineRule() - { - $translator = $this->getTranslator(['es_ES']); - $foo = $translator->getPluralForm(1); - $this->assertSame(1, $foo); - - // Test with 0. If `@PLURAL_RULE` 1 is applied, it will return `X_CARS.2` (zero is plural) - // With `@PLURAL_RULE` 0, it would have been `X_CARS.1` (no plurals) - // and with `@PLURAL_RULE` 2, would have been `X_CARS.1` also (0 is singular) - $this->assertEquals($translator->translate('X_CARS', 0), '0 coches'); - } - /** * Data Provider for testTwigFilters. * - * @return array + * @return mixed[] */ - public function twigProvider() + public function twigProvider(): array { return [ @@ -321,17 +372,15 @@ public function twigProvider() } /** - * @param array $language Default to ['en_US'], use ['en_US', 'fr_FR'] to french + * @param string $language Default to 'en_US'. Use 'fr_FR' for french * - * @return MessageTranslator + * @return Translator */ - protected function getTranslator($language = ['en_US']) + protected function getTranslator(string $language = 'en_US'): Translator { - $builder = new LocalePathBuilder($this->locator, 'locale://', $language); - $paths = $builder->buildPaths(); - $loader = new ArrayFileLoader($paths); - - $translator = new MessageTranslator($loader->load()); + $locale = new Locale($language); + $dictionary = new Dictionary($locale, $this->locator); + $translator = new Translator($dictionary); return $translator; } From b3300676a0d89052802ed9ab7838908535106bdc Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 29 Oct 2019 21:48:13 -0400 Subject: [PATCH 17/40] Try to avoid `Undefined index: parents` --- src/Locale.php | 6 +++++- tests/TranslatorTest.php | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Locale.php b/src/Locale.php index 65a96b1..9d2c170 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -126,7 +126,11 @@ public function getDependentLocales(): array */ public function getDependentLocalesIdentifier(): array { - return (is_array($this->config['parents'])) ? $this->config['parents'] : []; + if (isset($this->config['parents']) && is_array($this->config['parents'])) { + return $this->config['parents']; + } else { + return []; + } } /** diff --git a/tests/TranslatorTest.php b/tests/TranslatorTest.php index b47762c..32830ba 100644 --- a/tests/TranslatorTest.php +++ b/tests/TranslatorTest.php @@ -56,7 +56,7 @@ public function setUp() }*/ /** - * Test locale with a plural option + * Test locale with a plural option. */ public function testGetPluralForm(): void { @@ -118,7 +118,7 @@ public function testTranslate(string $key, $placeholders, string $expectedResult } /** - * Basic test to see if triple dependency works + * Basic test to see if triple dependency works. */ public function testTranslateWithNestedDependencies(): void { From f4e4b92aa59a7a3f4390a4714cad1ff744250aae Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 2 Nov 2019 14:36:43 -0400 Subject: [PATCH 18/40] Started moving Repository extension from Translator to Dictionary class --- src/Dictionary.php | 154 ++++++++++++++++++++++++++++++++++++ src/DictionaryInterface.php | 6 +- src/Translator.php | 18 ++--- 3 files changed, 168 insertions(+), 10 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index b077388..7c1844d 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -10,6 +10,7 @@ namespace UserFrosting\I18n; +use Illuminate\Support\Arr; use UserFrosting\Support\Repository\Loader\ArrayFileLoader; use UserFrosting\Support\Repository\Loader\FileRepositoryLoader; use UserFrosting\UniformResourceLocator\ResourceLocatorInterface; @@ -130,6 +131,7 @@ protected function loadDictionary(): array } // Now load dependent dictionnaries + // TODO : Split this in a sub method foreach ($this->locale->getDependentLocales() as $locale) { // Stop if locale already loaded to prevent recursion @@ -173,4 +175,156 @@ protected function getFiles(): array { return $this->locator->listResources($this->uri.$this->locale->getIndentifier(), true); } + + /** + * Determine if the given configuration value exists. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return Arr::has($this->all(), $key); + } + + /** + * Get the specified configuration value. + * + * @param array|string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (is_array($key)) { + return $this->getMany($key); + } + + return Arr::get($this->all(), $key, $default); + } + + /** + * Get many configuration values. + * + * @param array $keys + * @return array + */ + public function getMany($keys) + { + $config = []; + + foreach ($keys as $key => $default) { + if (is_numeric($key)) { + [$key, $default] = [$default, null]; + } + + $config[$key] = Arr::get($this->all(), $key, $default); + } + + return $config; + } + + /** + * Set a given configuration value. + * + * @param array|string $key + * @param mixed $value + * @return void + */ + public function set($key, $value = null) + { + $keys = is_array($key) ? $key : [$key => $value]; + $items = $this->all(); + + foreach ($keys as $key => $value) { + Arr::set($items, $key, $value); + } + } + + /** + * Prepend a value onto an array configuration value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function prepend($key, $value) + { + $array = $this->get($key); + + array_unshift($array, $value); + + $this->set($key, $array); + } + + /** + * Push a value onto an array configuration value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key); + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * Get all of the configuration items for the application. + * + * @return array + */ + public function all() + { + return $this->getDictionary(); + } + + /** + * Determine if the given configuration option exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Get a configuration option. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Set a configuration option. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + + /** + * Unset a configuration option. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + $this->set($key, null); + } } diff --git a/src/DictionaryInterface.php b/src/DictionaryInterface.php index c4bc3de..9729392 100644 --- a/src/DictionaryInterface.php +++ b/src/DictionaryInterface.php @@ -10,14 +10,18 @@ namespace UserFrosting\I18n; +use Illuminate\Contracts\Config\Repository; + /** * Locale Dictionary. * * Used to return all "Key => translation" data matrix + * Extend the Config repository to have acess to all the standard `has`, `get`, + * etc. public methods on the dictionnay array * * @author Louis Charette */ -interface DictionaryInterface +interface DictionaryInterface extends Repository { /** * Returns all loaded locale Key => Translation data dictionary. diff --git a/src/Translator.php b/src/Translator.php index 5dfc79c..3edc978 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -12,7 +12,6 @@ use Twig_Environment; use Twig_Loader_Filesystem; -use UserFrosting\Support\Repository\Repository; /** * Translator Class. @@ -22,7 +21,7 @@ * @author Louis Charette * @author Alexander Weissman (https://alexanderweissman.com) */ -class Translator extends Repository +class Translator { /** * @var Twig_Environment A Twig environment used to replace placeholders. @@ -46,8 +45,9 @@ class Translator extends Repository */ public function __construct(DictionaryInterface $dictionary) { + // Make sure dictionary is loaded $this->dictionary = $dictionary; - $this->items = $dictionary->getDictionary(); + $this->dictionary->getDictionary(); // Preapre Twig Environment $loader = new Twig_Loader_Filesystem(); @@ -88,12 +88,12 @@ public function translate(string $messageKey, $placeholders = []): string protected function getMessageFromKey(string $messageKey, &$placeholders): string { // If we can't find a match, return $messageKey - if (!$this->has($messageKey)) { + if (!$this->dictionary->has($messageKey)) { return $messageKey; } // Get message from items - $message = $this->get($messageKey); + $message = $this->dictionary->get($messageKey); // If message is an array, we'll need to go depper to get the actual string. Otherwise we're good to move on. if (!is_array($message)) { @@ -114,8 +114,8 @@ protected function getMessageFromKey(string $messageKey, &$placeholders): string if (is_null($pluralValue)) { // If we have a `@TRANSLATION` instruction, return this - if ($this->has($messageKey.'.@TRANSLATION') && !is_null($this->get($messageKey.'.@TRANSLATION'))) { - return $this->get($messageKey.'.@TRANSLATION'); + if ($this->dictionary->has($messageKey.'.@TRANSLATION') && !is_null($this->dictionary->get($messageKey.'.@TRANSLATION'))) { + return $this->dictionary->get($messageKey.'.@TRANSLATION'); } // Otherwise fallback to singular version @@ -143,8 +143,8 @@ protected function getMessageFromKey(string $messageKey, &$placeholders): string } // If we didn't find a plural form, we try to find the "@TRANSLATION" form. - if ($this->has($messageKey.'.@TRANSLATION')) { - return $this->get($messageKey.'.@TRANSLATION'); + if ($this->dictionary->has($messageKey.'.@TRANSLATION')) { + return $this->dictionary->get($messageKey.'.@TRANSLATION'); } // If the message is an array, but we can't find a plural form or a "@TRANSLATION" instruction, we can't go further. From 03482fe173c2c1bf79f9085f1df7ba8962f7f7ec Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 4 Nov 2019 19:53:54 -0500 Subject: [PATCH 19/40] Make all param from the config file optional --- composer.json | 2 +- src/Locale.php | 20 ++++++++++++++--- tests/LocaleTest.php | 22 +++++++++++-------- .../data/sprinkles/core/locale/de_DE/foo.yaml | 0 .../data/sprinkles/core/locale/de_DE/test.php | 13 +++++++++++ 5 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 tests/data/sprinkles/core/locale/de_DE/foo.yaml create mode 100644 tests/data/sprinkles/core/locale/de_DE/test.php diff --git a/composer.json b/composer.json index c7415a4..e571b9c 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.1", "twig/twig": "^2.11", - "userfrosting/support": "~4.3.0" + "userfrosting/support": "~4.3.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", diff --git a/src/Locale.php b/src/Locale.php index 9d2c170..076d8e6 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -69,7 +69,11 @@ protected function loadConfig(): void */ public function getAuthors(): array { - return $this->config['authors']; + if (!isset($this->config['authors'])) { + return []; + } else { + return $this->config['authors']; + } } /** @@ -140,7 +144,11 @@ public function getDependentLocalesIdentifier(): array */ public function getName(): string { - return $this->config['name']; + if (!isset($this->config['name'])) { + return ''; + } else { + return $this->config['name']; + } } /** @@ -164,6 +172,12 @@ public function getPluralRule(): int */ public function getLocalizedName(): string { - return $this->config['localized_name']; + if (isset($this->config['localized_name'])) { + return $this->config['localized_name']; + } elseif(isset($this->config['name'])) { + return $this->config['name']; + } else { + return $this->identifier; + } } } diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index 61a4c88..ee2180b 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -177,17 +177,21 @@ public function testGetDependentLocales(Locale $locale): void $this->assertInstanceOf(LocaleInterface::class, $result[0]); } - /* - * @depends testConstructor - * @depends testGetConfig - */ - /*public function testGetDictionary(Locale $locale) + public function testConstructorWithCustomFile() { - $dictionary = $locale->getDictionary(); - $this->assertInternalType('array', $dictionary); - + $locale = new Locale('de_DE', 'locale://de_DE/foo.yaml'); + $this->assertInstanceOf(LocaleInterface::class, $locale); - }*/ + $this->assertSame([], $locale->getAuthors()); + $this->assertSame('locale://de_DE/foo.yaml', $locale->getConfigFile()); + $this->assertSame('de_DE', $locale->getIndentifier()); + $this->assertSame([], $locale->getConfig()); + $this->assertSame([], $locale->getDependentLocales()); + $this->assertSame([], $locale->getDependentLocalesIdentifier()); + $this->assertSame('', $locale->getName()); + $this->assertSame(1, $locale->getPluralRule()); + $this->assertSame('de_DE', $locale->getLocalizedName()); + } /* TODO : diff --git a/tests/data/sprinkles/core/locale/de_DE/foo.yaml b/tests/data/sprinkles/core/locale/de_DE/foo.yaml new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/sprinkles/core/locale/de_DE/test.php b/tests/data/sprinkles/core/locale/de_DE/test.php new file mode 100644 index 0000000..6034546 --- /dev/null +++ b/tests/data/sprinkles/core/locale/de_DE/test.php @@ -0,0 +1,13 @@ + 'Bar', +]; From f39241a3261834fb622a9149b1cc360c8264e767 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 4 Nov 2019 20:08:24 -0500 Subject: [PATCH 20/40] Added missing test for getLocalizedName --- src/Dictionary.php | 41 +++++++++++-------- src/Locale.php | 2 +- tests/LocaleTest.php | 9 ++++ .../data/dictionary/locale/es_ES/locale.yaml | 1 - .../sprinkles/core/locale/es_ES/locale.yaml | 1 - 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index 7c1844d..824b5c7 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -131,7 +131,6 @@ protected function loadDictionary(): array } // Now load dependent dictionnaries - // TODO : Split this in a sub method foreach ($this->locale->getDependentLocales() as $locale) { // Stop if locale already loaded to prevent recursion @@ -179,7 +178,8 @@ protected function getFiles(): array /** * Determine if the given configuration value exists. * - * @param string $key + * @param string $key + * * @return bool */ public function has($key) @@ -190,8 +190,9 @@ public function has($key) /** * Get the specified configuration value. * - * @param array|string $key - * @param mixed $default + * @param array|string $key + * @param mixed $default + * * @return mixed */ public function get($key, $default = null) @@ -206,7 +207,8 @@ public function get($key, $default = null) /** * Get many configuration values. * - * @param array $keys + * @param array $keys + * * @return array */ public function getMany($keys) @@ -227,8 +229,9 @@ public function getMany($keys) /** * Set a given configuration value. * - * @param array|string $key - * @param mixed $value + * @param array|string $key + * @param mixed $value + * * @return void */ public function set($key, $value = null) @@ -244,8 +247,9 @@ public function set($key, $value = null) /** * Prepend a value onto an array configuration value. * - * @param string $key - * @param mixed $value + * @param string $key + * @param mixed $value + * * @return void */ public function prepend($key, $value) @@ -260,8 +264,9 @@ public function prepend($key, $value) /** * Push a value onto an array configuration value. * - * @param string $key - * @param mixed $value + * @param string $key + * @param mixed $value + * * @return void */ public function push($key, $value) @@ -286,7 +291,8 @@ public function all() /** * Determine if the given configuration option exists. * - * @param string $key + * @param string $key + * * @return bool */ public function offsetExists($key) @@ -297,7 +303,8 @@ public function offsetExists($key) /** * Get a configuration option. * - * @param string $key + * @param string $key + * * @return mixed */ public function offsetGet($key) @@ -308,8 +315,9 @@ public function offsetGet($key) /** * Set a configuration option. * - * @param string $key - * @param mixed $value + * @param string $key + * @param mixed $value + * * @return void */ public function offsetSet($key, $value) @@ -320,7 +328,8 @@ public function offsetSet($key, $value) /** * Unset a configuration option. * - * @param string $key + * @param string $key + * * @return void */ public function offsetUnset($key) diff --git a/src/Locale.php b/src/Locale.php index 076d8e6..e77e1a2 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -174,7 +174,7 @@ public function getLocalizedName(): string { if (isset($this->config['localized_name'])) { return $this->config['localized_name']; - } elseif(isset($this->config['name'])) { + } elseif (isset($this->config['name'])) { return $this->config['name']; } else { return $this->identifier; diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index ee2180b..f97e2e4 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -145,6 +145,15 @@ public function testGetDetails(Locale $locale): void $this->assertSame(2, $locale->getPluralRule()); } + /** + * @depends testGetDetails + */ + public function testGetLocalizedNameWithNoLocalizedConfig(): void + { + $locale = new Locale('es_ES'); + $this->assertSame('Spanish', $locale->getLocalizedName()); + } + /** * @depends testConstructor * @depends testGetConfig diff --git a/tests/data/dictionary/locale/es_ES/locale.yaml b/tests/data/dictionary/locale/es_ES/locale.yaml index aa56290..f5e4a0d 100644 --- a/tests/data/dictionary/locale/es_ES/locale.yaml +++ b/tests/data/dictionary/locale/es_ES/locale.yaml @@ -1,5 +1,4 @@ name: Spanish -localized_name: Español authors: - Salma Hayek - Selena Gomez diff --git a/tests/data/sprinkles/core/locale/es_ES/locale.yaml b/tests/data/sprinkles/core/locale/es_ES/locale.yaml index aa56290..f5e4a0d 100644 --- a/tests/data/sprinkles/core/locale/es_ES/locale.yaml +++ b/tests/data/sprinkles/core/locale/es_ES/locale.yaml @@ -1,5 +1,4 @@ name: Spanish -localized_name: Español authors: - Salma Hayek - Selena Gomez From 5a7fdec104da2ce860439d86dede3ab7e202d8a8 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 4 Nov 2019 20:26:18 -0500 Subject: [PATCH 21/40] Make Dictionary uses Support methods --- src/Dictionary.php | 174 ++------------------------------------------- 1 file changed, 6 insertions(+), 168 deletions(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index 824b5c7..71d91b4 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -10,7 +10,7 @@ namespace UserFrosting\I18n; -use Illuminate\Support\Arr; +use UserFrosting\Support\Repository\Repository; use UserFrosting\Support\Repository\Loader\ArrayFileLoader; use UserFrosting\Support\Repository\Loader\FileRepositoryLoader; use UserFrosting\UniformResourceLocator\ResourceLocatorInterface; @@ -22,7 +22,7 @@ * * @author Louis Charette */ -class Dictionary implements DictionaryInterface +class Dictionary extends Repository implements DictionaryInterface { /** * @var string Base URI for locator @@ -47,7 +47,7 @@ class Dictionary implements DictionaryInterface /** * @var array Locale "Key => translation" data matrix cache */ - protected $dictionary = []; + protected $items = []; /** * @param LocaleInterface $locale @@ -69,11 +69,11 @@ public function __construct(LocaleInterface $locale, ResourceLocatorInterface $l */ public function getDictionary(): array { - if (empty($this->dictionary)) { - $this->dictionary = $this->loadDictionary(); + if (empty($this->items)) { + $this->items = $this->loadDictionary(); } - return $this->dictionary; + return $this->items; } /** @@ -174,166 +174,4 @@ protected function getFiles(): array { return $this->locator->listResources($this->uri.$this->locale->getIndentifier(), true); } - - /** - * Determine if the given configuration value exists. - * - * @param string $key - * - * @return bool - */ - public function has($key) - { - return Arr::has($this->all(), $key); - } - - /** - * Get the specified configuration value. - * - * @param array|string $key - * @param mixed $default - * - * @return mixed - */ - public function get($key, $default = null) - { - if (is_array($key)) { - return $this->getMany($key); - } - - return Arr::get($this->all(), $key, $default); - } - - /** - * Get many configuration values. - * - * @param array $keys - * - * @return array - */ - public function getMany($keys) - { - $config = []; - - foreach ($keys as $key => $default) { - if (is_numeric($key)) { - [$key, $default] = [$default, null]; - } - - $config[$key] = Arr::get($this->all(), $key, $default); - } - - return $config; - } - - /** - * Set a given configuration value. - * - * @param array|string $key - * @param mixed $value - * - * @return void - */ - public function set($key, $value = null) - { - $keys = is_array($key) ? $key : [$key => $value]; - $items = $this->all(); - - foreach ($keys as $key => $value) { - Arr::set($items, $key, $value); - } - } - - /** - * Prepend a value onto an array configuration value. - * - * @param string $key - * @param mixed $value - * - * @return void - */ - public function prepend($key, $value) - { - $array = $this->get($key); - - array_unshift($array, $value); - - $this->set($key, $array); - } - - /** - * Push a value onto an array configuration value. - * - * @param string $key - * @param mixed $value - * - * @return void - */ - public function push($key, $value) - { - $array = $this->get($key); - - $array[] = $value; - - $this->set($key, $array); - } - - /** - * Get all of the configuration items for the application. - * - * @return array - */ - public function all() - { - return $this->getDictionary(); - } - - /** - * Determine if the given configuration option exists. - * - * @param string $key - * - * @return bool - */ - public function offsetExists($key) - { - return $this->has($key); - } - - /** - * Get a configuration option. - * - * @param string $key - * - * @return mixed - */ - public function offsetGet($key) - { - return $this->get($key); - } - - /** - * Set a configuration option. - * - * @param string $key - * @param mixed $value - * - * @return void - */ - public function offsetSet($key, $value) - { - $this->set($key, $value); - } - - /** - * Unset a configuration option. - * - * @param string $key - * - * @return void - */ - public function offsetUnset($key) - { - $this->set($key, null); - } } From aba43d5f2f36ce3f47c192c7df8b1c67e1b8d366 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 4 Nov 2019 20:27:34 -0500 Subject: [PATCH 22/40] Satisfy StyleCI --- src/Dictionary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index 71d91b4..3728239 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -10,9 +10,9 @@ namespace UserFrosting\I18n; -use UserFrosting\Support\Repository\Repository; use UserFrosting\Support\Repository\Loader\ArrayFileLoader; use UserFrosting\Support\Repository\Loader\FileRepositoryLoader; +use UserFrosting\Support\Repository\Repository; use UserFrosting\UniformResourceLocator\ResourceLocatorInterface; /** From 107dafd30669ecd94041dec2b6f7a1654b89a6de Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 4 Nov 2019 20:42:35 -0500 Subject: [PATCH 23/40] Added API docs --- README.md | 2 + composer.json | 3 +- docs/README.md | 5 + docs/api.md | 319 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 docs/README.md create mode 100644 docs/api.md diff --git a/README.md b/README.md index 74b260c..a9fbcb3 100644 --- a/README.md +++ b/README.md @@ -429,3 +429,5 @@ There's a child and no adults in the white Honda Civic 1993 ## [Style Guide](STYLE-GUIDE.md) ## [Testing](RUNNING_TESTS.md) + +## [API docs](docs/api.md) diff --git a/composer.json b/composer.json index e571b9c..92255ad 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.13", "mockery/mockery": "^1.2", - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^7.5", + "victorjonsson/markdowndocs": "^1.3" }, "autoload": { "psr-4": { diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..7428073 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ +# Building doc + +``` +vendor/bin/phpdoc-md generate src/ > docs/api.md +``` diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..5cd3038 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,319 @@ +## Table of contents + +- [\UserFrosting\I18n\LocaleInterface (interface)](#interface-userfrostingi18nlocaleinterface) +- [\UserFrosting\I18n\Dictionary](#class-userfrostingi18ndictionary) +- [\UserFrosting\I18n\DictionaryInterface (interface)](#interface-userfrostingi18ndictionaryinterface) +- [\UserFrosting\I18n\Translator](#class-userfrostingi18ntranslator) +- [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale) +- [\UserFrosting\I18n\PluralRules\Rule0](#class-userfrostingi18npluralrulesrule0) +- [\UserFrosting\I18n\PluralRules\Rule1](#class-userfrostingi18npluralrulesrule1) +- [\UserFrosting\I18n\PluralRules\Rule3](#class-userfrostingi18npluralrulesrule3) +- [\UserFrosting\I18n\PluralRules\Rule2](#class-userfrostingi18npluralrulesrule2) +- [\UserFrosting\I18n\PluralRules\Rule6](#class-userfrostingi18npluralrulesrule6) +- [\UserFrosting\I18n\PluralRules\Rule7](#class-userfrostingi18npluralrulesrule7) +- [\UserFrosting\I18n\PluralRules\Rule5](#class-userfrostingi18npluralrulesrule5) +- [\UserFrosting\I18n\PluralRules\Rule4](#class-userfrostingi18npluralrulesrule4) +- [\UserFrosting\I18n\PluralRules\Rule14](#class-userfrostingi18npluralrulesrule14) +- [\UserFrosting\I18n\PluralRules\RuleInterface (interface)](#interface-userfrostingi18npluralrulesruleinterface) +- [\UserFrosting\I18n\PluralRules\Rule15](#class-userfrostingi18npluralrulesrule15) +- [\UserFrosting\I18n\PluralRules\Rule12](#class-userfrostingi18npluralrulesrule12) +- [\UserFrosting\I18n\PluralRules\Rule13](#class-userfrostingi18npluralrulesrule13) +- [\UserFrosting\I18n\PluralRules\Rule11](#class-userfrostingi18npluralrulesrule11) +- [\UserFrosting\I18n\PluralRules\Rule10](#class-userfrostingi18npluralrulesrule10) +- [\UserFrosting\I18n\PluralRules\Rule9](#class-userfrostingi18npluralrulesrule9) +- [\UserFrosting\I18n\PluralRules\Rule8](#class-userfrostingi18npluralrulesrule8) + +
+ +### Interface: \UserFrosting\I18n\LocaleInterface + +> Locale interface. + +| Visibility | Function | +|:-----------|:---------| +| public | getAuthors() : string[] The list of authors
Returns the list of authors of the locale. | +| public | getConfig() : \UserFrosting\I18n\(array/\UserFrosting\I18n\string)[]
Return the raw configuration data. | +| public | getConfigFile() : string
Returns defined configuration file. | +| public | getDependentLocales() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)[]
Return an array of parent locales. | +| public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | +| public | getIndentifier() : string
Returns the locale indentifier. | +| public | getLocalizedName() : string
Return the localized version of the locale name. | +| public | getName() : string
Return the name of the locale, in English form. | +| public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | + +
+ +### Class: \UserFrosting\I18n\Dictionary + +> Locale Dictionary. Load all locale all "Key => translation" data matrix + +| Visibility | Function | +|:-----------|:---------| +| public | __construct([\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface) $locale, \UserFrosting\UniformResourceLocator\ResourceLocatorInterface $locator, \UserFrosting\Support\Repository\Loader\FileRepositoryLoader $fileLoader=null) : void | +| public | getDictionary() : string[] The locale dictionary
Returns all loaded locale Key => Translation data dictionary. Won't load the whole thing twice if already loaded in the class. | +| public | getFileLoader() : \UserFrosting\Support\Repository\Loader\FileRepositoryLoader
Return the file repository loader used to load. | +| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Return the associate locale. | +| public | setUri(\string $uri) : void
Set the locator base URI (default 'locale://'). | +| protected | filterDictionaryFiles(\UserFrosting\UniformResourceLocator\ResourceInterface[] $files) : string[]
Remove config files from locator results and convert ResourceInterface to path/string. | +| protected | getFiles() : \UserFrosting\UniformResourceLocator\ResourceInterface[]
List all files for a given locale using the locator. | +| protected | loadDictionary() : \UserFrosting\I18n\(string/array)[] The locale dictionary
Load the dictionary from file. | + +*This class extends \UserFrosting\Support\Repository\Repository* + +*This class implements \ArrayAccess, \Illuminate\Contracts\Config\Repository, [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)* + +
+ +### Interface: \UserFrosting\I18n\DictionaryInterface + +> Locale Dictionary. Used to return all "Key => translation" data matrix Extend the Config repository to have acess to all the standard `has`, `get`, etc. public methods on the dictionnay array + +| Visibility | Function | +|:-----------|:---------| +| public | getDictionary() : string[] The locale dictionary
Returns all loaded locale Key => Translation data dictionary. | +| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Return the associate locale. | + +*This class implements \Illuminate\Contracts\Config\Repository* + +
+ +### Class: \UserFrosting\I18n\Translator + +> Translator Class. Translate message ids to a message in a specified language. + +| Visibility | Function | +|:-----------|:---------| +| public | __construct([\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface) $dictionary) : void
Create the translator. | +| public | getPluralForm(int/float $number, bool/mixed $forceRule=false) : int The plural-case we need to use for the number plural-rule combination
Determine which plural form we should use. For some languages this is not as simple as for English. | +| public | translate(\string $messageKey, array/array/int $placeholders=array()) : string The translated message.
Translate the given message id into the currently configured language, substituting any placeholders that appear in the translated string. Return the $messageKey if not match is found | +| protected | getMessageFromKey(\string $messageKey, array/int $placeholders) : string The message string
Get the message from key. Go throught all registered language keys avaiable and find the correct one to use, using the placeholders to select the correct plural form. | +| protected | getPluralKey(array $messageArray) : string
Return the plural key from a translation array. If no plural key is defined in the `@PLURAL` instruction of the message array, we fallback to the default one. | +| protected | getPluralMessageKey(array $messageArray, \int $pluralValue) : int/null Returns which key from $messageArray to use
Return the correct plural message form to use. When multiple plural form are available for a message, this method will return the correct oen to use based on the numeric value. | +| protected | getPluralRuleNumber(int/bool $forceRule) : int
Return the correct rule number to use. | +| protected | getPluralValue(array/int $placeholders, \string $pluralKey) : int/null The number, null if not found
Return the plural value, aka the nummber to display, from the placeholder values. | +| protected | parsePlaceHolders(\string $message, array $placeholders) : string The message with replaced placeholders
Parse Placeholder. Replace placeholders in the message with their values from the passed argument. | + +
+ +### Class: \UserFrosting\I18n\Locale + +> Locale Class. Act as a container for a Locale data loaded from filesystem data + +| Visibility | Function | +|:-----------|:---------| +| public | __construct(\string $identifier, \string $configFile=null) : void
Create locale class. | +| public | getAuthors() : string[] The list of authors
Returns the list of authors of the locale. | +| public | getConfig() : \UserFrosting\I18n\(array/\UserFrosting\I18n\string)[]
Return the raw configuration data. | +| public | getConfigFile() : string
Returns defined configuration file. | +| public | getDependentLocales() : [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale)[]
Return an array of parent locales. | +| public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | +| public | getIndentifier() : string
Returns the locale indentifier. | +| public | getLocalizedName() : string
Return the localized version of the locale name. | +| public | getName() : string
Return the name of the locale, in English form. | +| public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | +| protected | loadConfig() : mixed
Loads the config into the class property. | + +*This class implements [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule0 + +> Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao 1 - everything: 0, 1, 2, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule1 + +> Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan) 1 - 1 2 - everything else: 0, 2, 3, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule3 + +> Families: Baltic (Latvian) 1 - 0 2 - ends in 1, not 11: 1, 21, ... 101, 121, ... 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule2 + +> Families: Romanic (French, Brazilian Portuguese) 1 - 0, 1 2 - everything else: 2, 3, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule6 + +> Families: Baltic (Lithuanian) 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ... 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule7 + +> Families: Slavic (Croatian, Serbian, Russian, Ukrainian) 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule5 + +> Families: Romanic (Romanian) 1 - 1 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ... 3 - everything else: 20, 21, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule4 + +> Families: Celtic (Scottish Gaelic) 1 - is 1 or 11: 1, 11 2 - is 2 or 12: 2, 12 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19 4 - everything else: 0, 20, 21, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule14 + +> Families: Slavic (Macedonian) 1 - ends in 1: 1, 11, 21, ... 2 - ends in 2: 2, 12, 22, ... 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Interface: \UserFrosting\I18n\PluralRules\RuleInterface + +> Interface for Rule Definition. The plural rules are based on a list published by the Mozilla Developer Network & code from phpBB Group + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(int $number) : int The rule
Return the rule to apply. | + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule15 + +> Families: Icelandic 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ... 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule12 + +> Families: Semitic (Arabic). 1 - 1 2 - 2 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ... 4 - ends in 11-99: 11, ... 99, 111, 112, ... 5 - everything else: 100, 101, 102, 200, 201, 202, ... 6 - 0 + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule13 + +> Families: Semitic (Maltese) 1 - 1 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ... 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ... 4 - everything else: 20, 21, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule11 + +> Families: Celtic (Irish Gaeilge) 1 - 1 2 - 2 3 - is 3-6: 3, 4, 5, 6 4 - is 7-10: 7, 8, 9, 10 5 - everything else: 0, 11, 12, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule10 + +> Families: Slavic (Slovenian, Sorbian) 1 - ends in 01: 1, 101, 201, ... 2 - ends in 02: 2, 102, 202, ... 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ... 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule9 + +> Families: Slavic (Polish) 1 - 1 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ... 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule8 + +> Families: Slavic (Slovak, Czech) 1 - 1 2 - 2, 3, 4 3 - everything else: 0, 5, 6, 7, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + From de2acd2de1c132b88a8e6736d3a4a9a94629df3f Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 9 Nov 2019 17:15:31 -0500 Subject: [PATCH 24/40] Updated Readme [ci skip] --- README.md | 209 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 171 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index a9fbcb3..0fea9b3 100644 --- a/README.md +++ b/README.md @@ -28,66 +28,143 @@ Louis Charette & Alexander Weissman, 2016-2019 -The I18n module handles translation tasks for UserFrosting. The `MessageTranslator` class can be used as follows: +
+ +The I18n module handles translation tasks for UserFrosting. + +The translator uses a Dictionary, which is itself tied to a Locale. The basic structure of the system can be represented this way : + +``` + |------------| + | Locale | + |------------| + | + V + |------------| |---------| |-------------| + | Dictionary | <-- | Locator | <-- | Definitions | + |------------| |---------| |-------------| + | + V + |------------| + | Translator | + |------------| +``` + +The **Locale** knows all there is to know about the locale itself. The name of the locale, the authors, the plural rules, etc. + +The **Dictionary** is tied to a specific locale. It's purpose is to return a data matrix composed of keys shared between all locales (called _message keys_) and the translated string (called _localized messages_). The system uses a `KEY` and `VALUE` system, which can be stored in standard PHP files. + +The **translator** use the data from the Dictionary to perform the actual translation, aka finding the proper key and replacing the placeholder with the specified values. + +
+ +Table of Contents +================= + + * [Documentation](#documentation) + * [Basic usage](#basic-usage) + * [Step 1 - Set up language file(s).](#step-1---set-up-language-files) + * [Step 2 - Set up the Locale:](#step-2---set-up-the-locale) + * [Step 3 - Set up UniformResourceLocator and the Dictionary:](#step-3---set-up-uniformresourcelocator-and-the-dictionary) + * [Step 4 - Initialize a Translator object:](#step-4---initialize-a-translator-object) + * [Step 5 - Do a translation!](#step-5---do-a-translation) + * [Wrap up](#wrap-up) + * [Locale configuration file](#locale-configuration-file) + * [Config values](#config-values) + * [name](#name) + * [localized_name](#localized_name) + * [authors](#authors) + * [plural_rule](#plural_rule) + * [parents](#parents) + * [Pluralization](#pluralization) + * [Plural value with placeholders](#plural-value-with-placeholders) + * [Multiple plural in a string](#multiple-plural-in-a-string) + * [Numbers are rules, not limits !](#numbers-are-rules-not-limits-) + * [One last thing about pluralization...](#one-last-thing-about-pluralization) + * [Sub keys](#sub-keys) + * [Handles](#handles) + * [@TRANSLATION](#translation) + * [@PLURAL](#plural) + * [The & placeholder](#the--placeholder) + * [Example of a complex translation](#example-of-a-complex-translation) + * [Language file](#language-file) + * [Translate function](#translate-function) + * [Result](#result) + * [Style Guide](STYLE-GUIDE.md) + * [Testing](RUNNING_TESTS.md) + * [API docs](docs/api.md) + +# Documentation ## Basic usage ### Step 1 - Set up language file(s). -A language file returns an array mapping _message keys_ to _localized messages_. Messages may optionally have placeholders. For example: +A language file returns an array mapping _message keys_ to _localized messages_. Messages may optionally have placeholders. For example: -**locale/es_ES/main.php** +**locale/en_US/main.php** ``` return array( - //MESSAGE_KEY => Localized message - "ACCOUNT_SPECIFY_USERNAME" => "Introduce tu nombre de usuario.", - "ACCOUNT_SPECIFY_DISPLAY_NAME" => "Introduce tu nombre público.", - "ACCOUNT_USER_CHAR_LIMIT" => "Tu nombre de usuario debe estar entre {{min}} y {{max}} caracteres de longitud." + //LANGUAGE_KEY => Localized message + + "ACCOUNT_SPECIFY_USERNAME" => "Please enter your user name.", + "ACCOUNT_SPECIFY_DISPLAY_NAME" => "Please enter your display name.", + "ACCOUNT_USER_CHAR_LIMIT" => "Your user name must be between {{min}} and {{max}} characters in length." ); ``` -**locale/en_US/main.php** - +**locale/es_ES/main.php** ``` return array( - //LANGUAGE_KEY => Localized message - "ACCOUNT_SPECIFY_USERNAME" => "Please enter your user name.", - "ACCOUNT_SPECIFY_DISPLAY_NAME" => "Please enter your display name.", - "ACCOUNT_USER_CHAR_LIMIT" => "Your user name must be between {{min}} and {{max}} characters in length." + //MESSAGE_KEY => Localized message + + "ACCOUNT_SPECIFY_USERNAME" => "Introduce tu nombre de usuario.", + "ACCOUNT_SPECIFY_DISPLAY_NAME" => "Introduce tu nombre público.", + "ACCOUNT_USER_CHAR_LIMIT" => "Tu nombre de usuario debe estar entre {{min}} y {{max}} caracteres de longitud." ); ``` -### Step 2 - Set up UniformResourceLocator and LocalePathBuilder classes: +### Step 2 - Set up the Locale: +The `Locale` class will load all [configuration value](#locale-configuration-file) for the specified locale. All you need to do is specify the locale identifier as a string : + +``` +$locale = new Locale('en_US'); +``` + +This will load all the English (`en_US`) locale config. When passed to the Dictionary, this will load all the English definitions. + +### Step 3 - Set up UniformResourceLocator and the Dictionary: + +The `Dictionary` will load all definitions for the specified locale. To achieve this, the dictionary will build a list of the files located in the locale identifier directory (eg. `en_US`) for the specified locator path. The [UniformResourceLocator](https://github.com/userfrosting/uniformresourcelocator) will be used to get the file list. + +First, you'll need to setup the Locator. See the [UniformResourceLocator documentation](https://github.com/userfrosting/UniformResourceLocator/tree/master/docs) for more information on this. ``` // Set up a locator class $locator = new UniformResourceLocator(__DIR__); -$locator->addPath('locale', '', 'locale'); +$locator->registerStream('locale'); +$locator->registerLocation('core'); -// Build paths to the desired languages -$builder = new LocalePathBuilder($locator, 'locale://', ['en_US']); +// Register the `__DIR__/core/locale/` path ``` -The `LocalePathBuilder` will build a list of the files located in the `locale/en_US` directory. You can also load paths for more than one language in the array passed to the constructor, or using the `addLocales` method. The languages will be merged together in the order in which they were added. For example: +With the locator and the locale, we can now create the Dictionary instance. ``` -$builder = new LocalePathBuilder($locator, 'locale://', ['en_US', 'es_ES']); +$dictionary = new Dictionary($locale, $this->locator); ``` -This will use the English (`en_US`) translation as a base and load the Spanish (`es_ES`) translation on top. All keys not found in the Spanish translation will fallback to the English one. +### Step 4 - Initialize a `Translator` object: -### Step 3 - Initialize a `MessageTranslator` object: +The translator can now be initiated with the Dictionary. The Locale will be inherited from the Dictionary. ``` -// Load and combine translation data from the locale files -$loader = new ArrayFileLoader($builder->buildPaths()); - -// Create the MessageTranslator object -$translator = new MessageTranslator($loader->load()); +// Create the Translator object +$translator = new Translator($dictionary); ``` -### Step 4 - Do a translation! +### Step 5 - Do a translation! ``` echo $translator->translate("ACCOUNT_USER_CHAR_LIMIT", [ @@ -98,11 +175,70 @@ echo $translator->translate("ACCOUNT_USER_CHAR_LIMIT", [ // Returns "Tu nombre de usuario debe estar entre 4 y 200 caracteres de longitud." ``` +### Wrap up + +``` +$locator = new UniformResourceLocator(__DIR__); +$locator->addPath('locale', '', 'locale'); + +$locale = new Locale('en_US'); +$dictionary = new Dictionary($locale, $locator); +$translator = new Translator($dictionary); + +echo $translator->translate("ACCOUNT_USER_CHAR_LIMIT", [ + "min" => 4, + "max" => 200 +]); +``` + +## Locale configuration file + +Each locale have it's own configuration file. Theses options are required to be saved in a `locale.yaml` file, located in the locale folder, accessible accessible using the `locale://en_US/locale.yaml` URI. + +The configuration file can contain multiple options. For example : + +``` +name: French Canadian +localized_name: Français Canadien +authors: + - Foo Bar + - Bar Foo +plural_rule: 2 +parents: + - fr_FR +``` + +### Config values + +#### `name` + +The name of the locale. Should be the English version of the name. + +#### `localized_name` + +The localized name of the locale. For example, for the French locale, the name of the locale in French. + +#### `authors` + +A list of authors for the locale. + +#### `plural_rule` + +The plural rule number associated with the locale. See [Pluralization](#Pluralization) for more details. + +#### `parents` + +A list of parents locales for this locale. Each locale data will be loaded on top of the parents one, including all dictionary definitions. + +For example, if the `fr_CA` locale has `fr_FR` as parent, all config and all keys not found in the `CA` translation will fallback to the `FR` one. If the `fr_FR` locale has `en_US` as parent itself, all keys not found in `CA` and `FR` will fallback to the English keys. + +It is recommended all locale have at least `en_US` as a top parent, so undefined keys in your locale will fallback to the English version. + ## Pluralization -The plural system allow for easy pluralization of strings. This whole system is based on Mozilla plural rules (See : https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals). For a given language, there is a grammatical rule on how to change words depending on the number qualifying the word. Different languages can have different rules. For example, in English you say `no cars` (note the plural `cars`) while in French you say `Aucune voiture` (note the singular `voiture`). +The plural system allow for easy pluralization of strings. This whole system is based on [Mozilla plural rules](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals). For a given language, there is a grammatical rule on how to change words depending on the number qualifying the word. Different languages can have different rules. For example, in English you say `no cars` (note the plural `cars`) while in French you say `Aucune voiture` (note the singular `voiture`). -The rule associated with a particular language ([see link above](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals)) is defined in the `@PLURAL_RULE` key. So in the `english` file, you should find `"@PLURAL_RULE" => 1` and in the `french` file `"@PLURAL_RULE" => 2`. +The rule associated with a particular language ([see link above](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals)) is defined in the [locale configuration metadata](#locale-configuration-file). So for the `english` locale, you should find `plural_rule: 1` and in the `french` file `"plural_rule: 2`. Strings with plural forms are defined as sub arrays with the rules as the key. The right plural form is determined by the plural value passed as the second parameter of the `translate` function : ``` @@ -206,7 +342,6 @@ In some cases, it could be faster and easier to directly access the plural value In this example, `$translator->translate("COLOR", 2);` and `$translator->translate("COLORS");` will return the same value. This is true for English, but not necessarily for all languages. While languages without any form of plural definitions will define something like `"COLOR" => "Color"` and `"COLORS" => "Color"`, some will have even more complicated rules. That's why it's always best to avoid keys like `COLORS` if you plan to translate to more than one language. This is also true with the `0` value that can be different across different language, but can also be handle differently depending of the message you want to display (Ex.: `No colors` instead of `0 colors`). - ## Sub keys Sub keys can be defined in language files for easier navigation of lists or to distinguish two items with common keys. For example: @@ -254,9 +389,6 @@ Of courses, sub keys and plural rules can live together inside the same master k ## Handles Some special handles can be defined in the languages files to modify the default behavior of the translator. These handle uses the `@` prefix. -### `@PLURAL_RULE` -See [Pluralization](#Pluralization). - ### `@TRANSLATION` If you want to give a value for the top level key, you can use the `@TRANSLATION` (handle)[#handles] which will create an alias `TOP_KEY` and point it to `TOP_KEY.@TRANSLATION`: ``` @@ -273,7 +405,7 @@ $translator->translate('ACCOUNT.@TRANSLATION') //Return "Account" $translator->translate('ACCOUNT.ALT'); //Return "Profile" ``` -N.B.: When `@TRANSLATION` is used with plural rules, omiting the second argument of the `translate` function will change the result. `1` will not be used as a plural value to determine which rule we chose. The `@TRANSLATION` value will be returned instead. For example: +N.B.: When `@TRANSLATION` is used with plural rules, omitting the second argument of the `translate` function will change the result. `1` will not be used as a plural value to determine which rule we chose. The `@TRANSLATION` value will be returned instead. For example: ``` "X_HUNGRY_CATS" => [ @@ -302,7 +434,7 @@ echo $translator->translate("NB_HUNGRY_CATS", ['nb': 5]); // Return "5 hungry ca ``` ### The `&` placeholder -When a placeholder name starts with the `&` character in translation files or the value of a placeholder starts with this same `&` character, it tells the translator to directly replace the placeholder with the message mapped by that message key (if found). Note that this is CASE SENSITIVE and, as with the other handles, all placeholders defined in the main translation function are passed to all child translations. This is useful when you don't want to translate the same word over and over again in the same language file or with complex translations with plural values. Be caureful when using this with plurals as the plural value is passed to all child translation and can cause conflict (See [Example of a complex translation](#example-of-a-complex-translation)). +When a placeholder name starts with the `&` character in translation files or the value of a placeholder starts with this same `&` character, it tells the translator to directly replace the placeholder with the message mapped by that message key (if found). Note that this is CASE SENSITIVE and, as with the other handles, all placeholders defined in the main translation function are passed to all child translations. This is useful when you don't want to translate the same word over and over again in the same language file or with complex translations with plural values. Be careful when using this with plurals as the plural value is passed to all child translation and can cause conflict (See [Example of a complex translation](#example-of-a-complex-translation)). Example: ``` @@ -317,19 +449,19 @@ $translator->translate('I_LOVE_MY_CATS', 3); //Return "I love my 3 cats" In this example, `{{&MY_CATS}}` gets replaced with the `MY_CATS` and since there's 3 cats, the n° 2 rule is selected. So the string becomes `I love my {{plural}} cats` which then becomes `I love my 3 cats`. -N.B.: Since this is the last thing handled by the translator, this behaviour can be overwritten by the function call: +N.B.: Since this is the last thing handled by the translator, this behavior can be overwritten by the function call: ``` $translator->translate('I_LOVE_MY_CATS', ["plural" => 3, "&MY_CATS" => "my 3 dogs"); //Return "I love my 3 dogs" ``` -Since the other placeholders, including the plural value(s) are also be passed to the sub translation, it can be useful for languages like french where the adjectives can also be pluralizable. Consider this sentence : `I have 3 white catS`. In french, we would say `J'ai 3 chatS blancS`. Notice the `S` on the color `blanc`? One developer could be tempted to do this in an English context : +Since the other placeholders, including the plural value(s) are also be passed to the sub translation, it can be useful for languages like french where the adjectives can also be "pluralizable". Consider this sentence : `I have 3 white catS`. In french, we would say `J'ai 3 chatS blancS`. Notice the `S` on the color `blanc`? One developer could be tempted to do this in an English context : ``` $colorString = $translator->translate('COLOR.WHITE'); echo $translator->translate('MY_CATS', ["plural" => 3, "color" => $colorString); ``` -While this would work in english because the color isn't pluralizable, it won't in french. We'll end up with `J'ai 3 chatS blanc` (No `S` on the color). What we need is the php code to call the translation and passing the color key as a placeholder using the `&` prefix : `$translator->translate('MY_CATS', ["plural" => 3, "color" => "&COLOR.WHITE"]);`. The languages files for both languages in this case would be: +While this would work in english because the color isn't "pluralizable", it won't in french. We'll end up with `J'ai 3 chatS blanc` (No `S` on the color). What we need is the php code to call the translation and passing the color key as a placeholder using the `&` prefix : `$translator->translate('MY_CATS', ["plural" => 3, "color" => "&COLOR.WHITE"]);`. The languages files for both languages in this case would be: _English_ ``` @@ -409,6 +541,7 @@ return [ ``` ### Translate function + ``` $carMake = "Honda"; echo $translator->translate("COMPLEX_STRING", [ From 7f98af6755df44f1ca9b02779c38b49365d4fb8d Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 9 Nov 2019 17:28:30 -0500 Subject: [PATCH 25/40] Updated Changelog [ci skip] --- CHANGELOG.md | 22 ++++++++++++++++++++++ README.md | 1 - 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 294102f..27f62ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.4.0] +Complete rewrite of the Translator. + +Instead of `LocalePathBuilder -> MessageTranslator`, the new translator introduce the concept of `Locale`, `Dictionary` and `Translator`. So now you create a locale, which enables you to create a dictionary for that locale, which finally allows you to create a translator which will used that dictionary. + +Instead of loading multiple locale on top of each other, a locale can depend on other locale using the configuration file (ie. `locale.yaml`). See the updated doc for more information. + +Plural rule are now defined in the locale configuration file instead of the special `@PLURAL_RULE` key. + +All methods of the `Translator` are the same for backward compatibility. The only change is the constructor, which now requires a `DictionaryInterface` instance. + +**Detailed changes** : +- `MessageTranslator` is now `Translator`. +- `Translator` requires a `DictionaryInterface` as some constructor argument instead of paths. +- `LocalePathBuilder` removed. +- `DictionaryInterface` now extends `UserFrosting\Support\Repository\Repository` instead of the `Translator`. The raw data can be accessed using the Dictionary methods. +- `@PLURAL_RULE` special key removed. Use the Locale configuration file (`locale.yaml`) `plural_rule` attribute instead. +- Translator can't load multiple locale anymore. Use the Locale configuration file `parents` attribute instead. + +See updated [documentation](README.md) for more details on how to use the new Translator, Locale and Dictionary. + ## [4.3.0] - Dropping support for PHP 5.6 & 7.0 - Updated Twig to 2.x @@ -36,6 +57,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## 4.0.0 - Initial release +[4.4.0]: https://github.com/userfrosting/i18n/compare/4.3.0...4.4.0 [4.3.0]: https://github.com/userfrosting/i18n/compare/4.2.1...4.3.0 [4.2.1]: https://github.com/userfrosting/i18n/compare/4.2.0...4.2.1 [4.2.0]: https://github.com/userfrosting/i18n/compare/4.1.0...4.2.0 diff --git a/README.md b/README.md index 0fea9b3..08585dc 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ Louis Charette & Alexander Weissman, 2016-2019 -
The I18n module handles translation tasks for UserFrosting. From e92a3b352a5d1d0f8dd24aa009c871a0faec58c3 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 9 Nov 2019 17:32:16 -0500 Subject: [PATCH 26/40] Added `getDictionary` & `getLocale` to translator. --- docs/api.md | 2 ++ src/Translator.php | 22 ++++++++++++++++++++++ tests/TranslatorTest.php | 16 +++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 5cd3038..6164551 100644 --- a/docs/api.md +++ b/docs/api.md @@ -84,6 +84,8 @@ | Visibility | Function | |:-----------|:---------| | public | __construct([\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface) $dictionary) : void
Create the translator. | +| public | getDictionary() : [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)
Returned the associated dictionary | +| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Returns the associated locale for the specified dictionary | | public | getPluralForm(int/float $number, bool/mixed $forceRule=false) : int The plural-case we need to use for the number plural-rule combination
Determine which plural form we should use. For some languages this is not as simple as for English. | | public | translate(\string $messageKey, array/array/int $placeholders=array()) : string The translated message.
Translate the given message id into the currently configured language, substituting any placeholders that appear in the translated string. Return the $messageKey if not match is found | | protected | getMessageFromKey(\string $messageKey, array/int $placeholders) : string The message string
Get the message from key. Go throught all registered language keys avaiable and find the correct one to use, using the placeholders to select the correct plural form. | diff --git a/src/Translator.php b/src/Translator.php index 3edc978..cf662a4 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -12,6 +12,8 @@ use Twig_Environment; use Twig_Loader_Filesystem; +use UserFrosting\I18n\LocaleInterface; +use UserFrosting\I18n\DictionaryInterface; /** * Translator Class. @@ -54,6 +56,26 @@ public function __construct(DictionaryInterface $dictionary) $this->twig = new Twig_Environment($loader); } + /** + * Returned the associated dictionary + * + * @return DictionaryInterface + */ + public function getDictionary(): DictionaryInterface + { + return $this->dictionary; + } + + /** + * Returns the associated locale for the specified dictionary + * + * @return LocaleInterface + */ + public function getLocale(): LocaleInterface + { + return $this->dictionary->getLocale(); + } + /** * Translate the given message id into the currently configured language, substituting any placeholders that appear in the translated string. * diff --git a/tests/TranslatorTest.php b/tests/TranslatorTest.php index 32830ba..91adf0d 100644 --- a/tests/TranslatorTest.php +++ b/tests/TranslatorTest.php @@ -55,6 +55,20 @@ public function setUp() ], $locales); }*/ + /** + * Test locale with a plural option. + */ + public function testGetDictionaryAndLocale(): void + { + $locale = new Locale('en_US'); + $dictionary = new Dictionary($locale, $this->locator); + $translator = new Translator($dictionary); + + $this->assertSame($dictionary, $translator->getDictionary()); + $this->assertSame($locale, $translator->getLocale()); + $this->assertSame($dictionary->getLocale(), $translator->getLocale()); + } + /** * Test locale with a plural option. */ @@ -75,7 +89,7 @@ public function testGetPluralForm(): void /** * @depends testGetPluralForm - * Test locale wihtout a `@PLURAL_RULE`. + * Test locale wihtout a plural rule. */ public function testGetPluralFormWithNoDefineRule(): void { From bb7933d71074635461df77010b6829a897b8d09d Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 9 Nov 2019 17:33:25 -0500 Subject: [PATCH 27/40] Making StyleCI happy --- src/Translator.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Translator.php b/src/Translator.php index cf662a4..ebb2e60 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -12,8 +12,6 @@ use Twig_Environment; use Twig_Loader_Filesystem; -use UserFrosting\I18n\LocaleInterface; -use UserFrosting\I18n\DictionaryInterface; /** * Translator Class. @@ -57,7 +55,7 @@ public function __construct(DictionaryInterface $dictionary) } /** - * Returned the associated dictionary + * Returned the associated dictionary. * * @return DictionaryInterface */ @@ -67,7 +65,7 @@ public function getDictionary(): DictionaryInterface } /** - * Returns the associated locale for the specified dictionary + * Returns the associated locale for the specified dictionary. * * @return LocaleInterface */ From b24d47e36db42f27f9ad2f432877f534d83827eb Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 9 Nov 2019 17:39:57 -0500 Subject: [PATCH 28/40] ['options']['plural'] => ['plural_rule'] --- src/Locale.php | 4 ++-- tests/LocaleTest.php | 4 +--- tests/TranslatorTest.php | 6 +++--- tests/data/dictionary/locale/en_US/locale.yaml | 3 +-- tests/data/dictionary/locale/fr_CA/locale.yaml | 3 +-- tests/data/dictionary/locale/fr_FR/locale.yaml | 3 +-- tests/data/sprinkles/account/locale/fr_FR/locale.yaml | 3 +-- tests/data/sprinkles/account/locale/fr_FR/test.php | 2 -- tests/data/sprinkles/core/locale/en_US/locale.yaml | 3 +-- tests/data/sprinkles/core/locale/en_US/test.php | 2 -- tests/data/sprinkles/core/locale/fr_FR/locale.yaml | 3 +-- tests/data/sprinkles/core/locale/fr_FR/test.php | 2 -- tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml | 3 +-- 13 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/Locale.php b/src/Locale.php index e77e1a2..1343bcb 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -158,8 +158,8 @@ public function getName(): string */ public function getPluralRule(): int { - if (isset($this->config['options']['plural'])) { - return $this->config['options']['plural']; + if (isset($this->config['plural_rule'])) { + return $this->config['plural_rule']; } else { return 1; } diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index f97e2e4..e447b10 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -96,9 +96,7 @@ public function testGetConfig(Locale $locale): void 'Foo Bar', 'Bar Foo', // Not available in `core` version ], - 'options' => [ - 'plural' => 2, - ], + 'plural_rule' => 2, 'parents' => [ 'en_US', ], diff --git a/tests/TranslatorTest.php b/tests/TranslatorTest.php index 91adf0d..9313340 100644 --- a/tests/TranslatorTest.php +++ b/tests/TranslatorTest.php @@ -79,9 +79,9 @@ public function testGetPluralForm(): void $this->assertSame(2, $translator->getPluralForm(2)); $this->assertSame(2, $translator->getPluralForm(20)); - // Test with 0. If `@PLURAL_RULE` 1 is applied, it will return `X_CARS.2` (zero is plural) - // With `@PLURAL_RULE` 0, it would have been `X_CARS.1` (no plurals) - // and with `@PLURAL_RULE` 2, would have been `X_CARS.1` also (0 is singular) + // Test with 0. If `plural_rule` 1 is applied, it will return `X_CARS.2` (zero is plural) + // With `plural_rule` 0, it would have been `X_CARS.1` (no plurals) + // and with `plural_rule` 2, would have been `X_CARS.1` also (0 is singular) $this->assertEquals($translator->translate('X_CARS', 0), 'no cars'); $this->assertEquals($translator->translate('X_CARS', 1), 'a car'); $this->assertEquals($translator->translate('X_CARS', 2), '2 cars'); diff --git a/tests/data/dictionary/locale/en_US/locale.yaml b/tests/data/dictionary/locale/en_US/locale.yaml index 95d02de..a381dce 100644 --- a/tests/data/dictionary/locale/en_US/locale.yaml +++ b/tests/data/dictionary/locale/en_US/locale.yaml @@ -2,5 +2,4 @@ name: English localized_name: English authors: - John Appleseed -options: - plural: 1 +plural_rule: 1 diff --git a/tests/data/dictionary/locale/fr_CA/locale.yaml b/tests/data/dictionary/locale/fr_CA/locale.yaml index c7cf5bf..b96b841 100644 --- a/tests/data/dictionary/locale/fr_CA/locale.yaml +++ b/tests/data/dictionary/locale/fr_CA/locale.yaml @@ -3,7 +3,6 @@ localized_name: Français Canadien authors: - Foo Bar - Bar Foo -options: - plural: 2 +plural_rule: 2 parents: - fr_FR diff --git a/tests/data/dictionary/locale/fr_FR/locale.yaml b/tests/data/dictionary/locale/fr_FR/locale.yaml index 4606d22..962ad21 100644 --- a/tests/data/dictionary/locale/fr_FR/locale.yaml +++ b/tests/data/dictionary/locale/fr_FR/locale.yaml @@ -3,7 +3,6 @@ localized_name: Français authors: - Foo Bar - Bar Foo -options: - plural: 2 +plural_rule: 2 parents: - en_US diff --git a/tests/data/sprinkles/account/locale/fr_FR/locale.yaml b/tests/data/sprinkles/account/locale/fr_FR/locale.yaml index 4606d22..962ad21 100644 --- a/tests/data/sprinkles/account/locale/fr_FR/locale.yaml +++ b/tests/data/sprinkles/account/locale/fr_FR/locale.yaml @@ -3,7 +3,6 @@ localized_name: Français authors: - Foo Bar - Bar Foo -options: - plural: 2 +plural_rule: 2 parents: - en_US diff --git a/tests/data/sprinkles/account/locale/fr_FR/test.php b/tests/data/sprinkles/account/locale/fr_FR/test.php index 0e7c591..2b8dc8b 100644 --- a/tests/data/sprinkles/account/locale/fr_FR/test.php +++ b/tests/data/sprinkles/account/locale/fr_FR/test.php @@ -9,8 +9,6 @@ */ return [ - '@PLURAL_RULE' => 2, //Required to get the right rule. French is 2, english is 1 - 'USERNAME' => 'Nom d\'utilisateur', //Note the espace `\` caracter here. Won't be displayed in the test //"BASE_FALLBACK" => "Langue de secours", //We want to test if the english string will be displayed here diff --git a/tests/data/sprinkles/core/locale/en_US/locale.yaml b/tests/data/sprinkles/core/locale/en_US/locale.yaml index 95d02de..a381dce 100644 --- a/tests/data/sprinkles/core/locale/en_US/locale.yaml +++ b/tests/data/sprinkles/core/locale/en_US/locale.yaml @@ -2,5 +2,4 @@ name: English localized_name: English authors: - John Appleseed -options: - plural: 1 +plural_rule: 1 diff --git a/tests/data/sprinkles/core/locale/en_US/test.php b/tests/data/sprinkles/core/locale/en_US/test.php index 8ce57ec..0ca7c8b 100644 --- a/tests/data/sprinkles/core/locale/en_US/test.php +++ b/tests/data/sprinkles/core/locale/en_US/test.php @@ -9,8 +9,6 @@ */ return [ - '@PLURAL_RULE' => 1, //Required to get the right rule. French is 2, english is 1 - 'USERNAME' => 'Username', 'BASE_FALLBACK' => 'Base fallback', diff --git a/tests/data/sprinkles/core/locale/fr_FR/locale.yaml b/tests/data/sprinkles/core/locale/fr_FR/locale.yaml index 2be1d1c..68f3311 100644 --- a/tests/data/sprinkles/core/locale/fr_FR/locale.yaml +++ b/tests/data/sprinkles/core/locale/fr_FR/locale.yaml @@ -2,7 +2,6 @@ name: French localized_name: Français authors: - Foo Bar -options: - plural: 2 +plural_rule: 2 parents: - en_US diff --git a/tests/data/sprinkles/core/locale/fr_FR/test.php b/tests/data/sprinkles/core/locale/fr_FR/test.php index 0e7c591..2b8dc8b 100644 --- a/tests/data/sprinkles/core/locale/fr_FR/test.php +++ b/tests/data/sprinkles/core/locale/fr_FR/test.php @@ -9,8 +9,6 @@ */ return [ - '@PLURAL_RULE' => 2, //Required to get the right rule. French is 2, english is 1 - 'USERNAME' => 'Nom d\'utilisateur', //Note the espace `\` caracter here. Won't be displayed in the test //"BASE_FALLBACK" => "Langue de secours", //We want to test if the english string will be displayed here diff --git a/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml index c7cf5bf..b96b841 100644 --- a/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml +++ b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml @@ -3,7 +3,6 @@ localized_name: Français Canadien authors: - Foo Bar - Bar Foo -options: - plural: 2 +plural_rule: 2 parents: - fr_FR From 9d517f27471986da8666a2d3b4108d90fdc9bc66 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sat, 9 Nov 2019 17:41:01 -0500 Subject: [PATCH 29/40] Making StyleCI Happy --- tests/LocaleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index e447b10..d3ebf69 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -97,7 +97,7 @@ public function testGetConfig(Locale $locale): void 'Bar Foo', // Not available in `core` version ], 'plural_rule' => 2, - 'parents' => [ + 'parents' => [ 'en_US', ], ], $data); From d59659a7b6f7f3ba474885694256962511230cd2 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Fri, 22 Nov 2019 20:01:32 -0500 Subject: [PATCH 30/40] Fix typo --- docs/api.md | 8 ++++---- src/Dictionary.php | 8 ++++---- src/Locale.php | 4 ++-- src/LocaleInterface.php | 4 ++-- tests/DictionaryTest.php | 34 +++++++++++++++++----------------- tests/LocaleTest.php | 6 +++--- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/api.md b/docs/api.md index 6164551..35ae3c8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -36,7 +36,7 @@ | public | getConfigFile() : string
Returns defined configuration file. | | public | getDependentLocales() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)[]
Return an array of parent locales. | | public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | -| public | getIndentifier() : string
Returns the locale indentifier. | +| public | getIdentifier() : string
Returns the locale identifier. | | public | getLocalizedName() : string
Return the localized version of the locale name. | | public | getName() : string
Return the name of the locale, in English form. | | public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | @@ -84,8 +84,8 @@ | Visibility | Function | |:-----------|:---------| | public | __construct([\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface) $dictionary) : void
Create the translator. | -| public | getDictionary() : [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)
Returned the associated dictionary | -| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Returns the associated locale for the specified dictionary | +| public | getDictionary() : [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)
Returned the associated dictionary. | +| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Returns the associated locale for the specified dictionary. | | public | getPluralForm(int/float $number, bool/mixed $forceRule=false) : int The plural-case we need to use for the number plural-rule combination
Determine which plural form we should use. For some languages this is not as simple as for English. | | public | translate(\string $messageKey, array/array/int $placeholders=array()) : string The translated message.
Translate the given message id into the currently configured language, substituting any placeholders that appear in the translated string. Return the $messageKey if not match is found | | protected | getMessageFromKey(\string $messageKey, array/int $placeholders) : string The message string
Get the message from key. Go throught all registered language keys avaiable and find the correct one to use, using the placeholders to select the correct plural form. | @@ -109,7 +109,7 @@ | public | getConfigFile() : string
Returns defined configuration file. | | public | getDependentLocales() : [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale)[]
Return an array of parent locales. | | public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | -| public | getIndentifier() : string
Returns the locale indentifier. | +| public | getIdentifier() : string
Returns the locale identifier. | | public | getLocalizedName() : string
Return the localized version of the locale name. | | public | getName() : string
Return the name of the locale, in English form. | | public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | diff --git a/src/Dictionary.php b/src/Dictionary.php index 3728239..eea18ff 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -116,7 +116,7 @@ protected function loadDictionary(): array $dictionary = []; // List of loaded locales - $loadedLocale = [$this->locale->getIndentifier()]; + $loadedLocale = [$this->locale->getIdentifier()]; // Get list of files to load $files = $this->getFiles(); @@ -134,7 +134,7 @@ protected function loadDictionary(): array foreach ($this->locale->getDependentLocales() as $locale) { // Stop if locale already loaded to prevent recursion - $localesToLoad = array_merge([$locale->getIndentifier()], $locale->getDependentLocalesIdentifier()); + $localesToLoad = array_merge([$locale->getIdentifier()], $locale->getDependentLocalesIdentifier()); $intersection = array_intersect($localesToLoad, $loadedLocale); if (!empty($intersection)) { throw new \LogicException("Can't load dictionary. Dependencies recursion detected : ".implode(', ', $intersection)); @@ -143,7 +143,7 @@ protected function loadDictionary(): array $dependentDictionary = new self($locale, $this->locator, $this->fileLoader); $dictionary = array_replace_recursive($dependentDictionary->getDictionary(), $dictionary); - $loadedLocale[] = $locale->getIndentifier(); + $loadedLocale[] = $locale->getIdentifier(); } return $dictionary; @@ -172,6 +172,6 @@ protected function filterDictionaryFiles(array $files): array */ protected function getFiles(): array { - return $this->locator->listResources($this->uri.$this->locale->getIndentifier(), true); + return $this->locator->listResources($this->uri.$this->locale->getIdentifier(), true); } } diff --git a/src/Locale.php b/src/Locale.php index 1343bcb..142917e 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -87,11 +87,11 @@ public function getConfigFile(): string } /** - * Returns the locale indentifier. + * Returns the locale identifier. * * @return string */ - public function getIndentifier(): string + public function getIdentifier(): string { return $this->identifier; } diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index d3b5351..0a2331d 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -32,11 +32,11 @@ public function getAuthors(): array; public function getConfigFile(): string; /** - * Returns the locale indentifier. + * Returns the locale identifier. * * @return string */ - public function getIndentifier(): string; + public function getIdentifier(): string; /** * Return the raw configuration data. diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index afeb872..81c805e 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -70,7 +70,7 @@ public function testGetDictionary_withNoDependentLocaleNoData(): void $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare mock Locator - Return no file $locator = Mockery::mock(ResourceLocator::class); @@ -102,7 +102,7 @@ public function testSetUri(): void $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare mock Locator - Return no file $locator = Mockery::mock(ResourceLocator::class); @@ -138,7 +138,7 @@ public function testGetDictionary_withNoDependentLocaleWithData(): void $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare mock Resource - File `Foo/Bar/File1.php` $file = Mockery::mock(Resource::class); @@ -182,7 +182,7 @@ public function testGetDictionary_withNoDependentLocaleWithManyFiles(): void $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare first mock Resource - File `Foo/Bar/File1.php` $file1 = Mockery::mock(Resource::class); @@ -226,13 +226,13 @@ public function testGetDictionary_withDependentLocaleNoDataOnBoth(): void $localeDependent = Mockery::mock(Locale::class); $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + $localeDependent->shouldReceive('getIdentifier')->andReturn('ff_FF'); // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare mock Locator - Return no file $locator = Mockery::mock(ResourceLocator::class); @@ -274,13 +274,13 @@ public function testGetDictionary_withDependentLocaleAndDataOnAA(): void $localeDependent = Mockery::mock(Locale::class); $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + $localeDependent->shouldReceive('getIdentifier')->andReturn('ff_FF'); // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare first mock Resource - File `Foo/Bar/File1.php` $file1 = Mockery::mock(Resource::class); @@ -324,13 +324,13 @@ public function testGetDictionary_withDependentLocaleAndDataOnFF(): void $localeDependent = Mockery::mock(Locale::class); $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + $localeDependent->shouldReceive('getIdentifier')->andReturn('ff_FF'); // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare first mock Resource - File `Foo/Bar/File1.php` $file1 = Mockery::mock(Resource::class); @@ -395,13 +395,13 @@ public function testGetDictionary_withDependentLocaleDataOnBoth(): void $localeDependent = Mockery::mock(Locale::class); $localeDependent->shouldReceive('getDependentLocales')->andReturn([]); $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $localeDependent->shouldReceive('getIndentifier')->andReturn('en_US'); + $localeDependent->shouldReceive('getIdentifier')->andReturn('en_US'); // Prepare mocked locale - fr_FR $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['en_US']); - $locale->shouldReceive('getIndentifier')->andReturn('fr_FR'); + $locale->shouldReceive('getIdentifier')->andReturn('fr_FR'); // Prepare first mock Resource - File `Foo/Bar/File1.php` $file_FR = Mockery::mock(Resource::class); @@ -444,19 +444,19 @@ public function testGetDictionary_withManyDependentLocale(): void $localeSubDependent = Mockery::mock(Locale::class); $localeSubDependent->shouldReceive('getDependentLocales')->andReturn([]); $localeSubDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn([]); - $localeSubDependent->shouldReceive('getIndentifier')->andReturn('ee_EE'); + $localeSubDependent->shouldReceive('getIdentifier')->andReturn('ee_EE'); // Prepare dependent mocked locale - ff_FF $localeDependent = Mockery::mock(Locale::class); $localeDependent->shouldReceive('getDependentLocales')->andReturn([$localeSubDependent]); $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ee_EE']); - $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + $localeDependent->shouldReceive('getIdentifier')->andReturn('ff_FF'); // Prepare mocked locale - aa_bb $locale = Mockery::mock(Locale::class); $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare mock Locator - Return no file on ff_FF $locator = Mockery::mock(ResourceLocator::class); @@ -499,11 +499,11 @@ public function testGetDictionary_withRecursiveDependentLocale(): void $localeDependent->shouldReceive('getDependentLocales')->andReturn([$locale]); $localeDependent->shouldReceive('getDependentLocalesIdentifier')->andReturn(['aa_bb']); - $localeDependent->shouldReceive('getIndentifier')->andReturn('ff_FF'); + $localeDependent->shouldReceive('getIdentifier')->andReturn('ff_FF'); $locale->shouldReceive('getDependentLocales')->andReturn([$localeDependent]); $locale->shouldReceive('getDependentLocalesIdentifier')->andReturn(['ff_FF']); - $locale->shouldReceive('getIndentifier')->andReturn('aa_bb'); + $locale->shouldReceive('getIdentifier')->andReturn('aa_bb'); // Prepare first mock Resource - File `Foo/Bar/File1.php` $file1 = Mockery::mock(Resource::class); diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index d3ebf69..6a7de7c 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -73,9 +73,9 @@ public function testConstructorWithNotPath(): void /** * @depends testConstructor */ - public function testGetIndentifier(Locale $locale): void + public function testGetIdentifier(Locale $locale): void { - $data = $locale->getIndentifier(); + $data = $locale->getIdentifier(); $this->assertIsString($data); $this->assertSame('fr_FR', $data); @@ -191,7 +191,7 @@ public function testConstructorWithCustomFile() $this->assertSame([], $locale->getAuthors()); $this->assertSame('locale://de_DE/foo.yaml', $locale->getConfigFile()); - $this->assertSame('de_DE', $locale->getIndentifier()); + $this->assertSame('de_DE', $locale->getIdentifier()); $this->assertSame([], $locale->getConfig()); $this->assertSame([], $locale->getDependentLocales()); $this->assertSame([], $locale->getDependentLocalesIdentifier()); From 295650ed2127b4dcfb5165aa8e9b703f3622f7ce Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Fri, 22 Nov 2019 20:18:50 -0500 Subject: [PATCH 31/40] Added PHP 7.4 to travis config --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index fa2ae23..51b5702 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,15 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4snapshot +matrix: + fast_finish: true + + # Remove before UF 4.4.0 official release, and after 7.4 release ! + allow_failures: + - php: 7.4snapshot + cache: directories: - $HOME/.composer/cache From 29904d5240e1c909d73232888a6a7829c4233bc9 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 10 Dec 2019 20:17:02 -0500 Subject: [PATCH 32/40] localized_name -> regional --- README.md | 6 +++--- docs/api.md | 4 ++-- src/Locale.php | 6 +++--- src/LocaleInterface.php | 2 +- tests/LocaleTest.php | 12 ++++++------ tests/data/dictionary/locale/en_US/locale.yaml | 2 +- tests/data/dictionary/locale/fr_CA/locale.yaml | 2 +- tests/data/dictionary/locale/fr_FR/locale.yaml | 2 +- .../data/sprinkles/account/locale/fr_FR/locale.yaml | 2 +- tests/data/sprinkles/core/locale/en_US/locale.yaml | 2 +- tests/data/sprinkles/core/locale/fr_FR/locale.yaml | 2 +- tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml | 2 +- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 08585dc..a48fe3f 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Table of Contents * [Locale configuration file](#locale-configuration-file) * [Config values](#config-values) * [name](#name) - * [localized_name](#localized_name) + * [regional](#regional) * [authors](#authors) * [plural_rule](#plural_rule) * [parents](#parents) @@ -198,7 +198,7 @@ The configuration file can contain multiple options. For example : ``` name: French Canadian -localized_name: Français Canadien +regional: Français Canadien authors: - Foo Bar - Bar Foo @@ -213,7 +213,7 @@ parents: The name of the locale. Should be the English version of the name. -#### `localized_name` +#### `regional` The localized name of the locale. For example, for the French locale, the name of the locale in French. diff --git a/docs/api.md b/docs/api.md index 35ae3c8..0484159 100644 --- a/docs/api.md +++ b/docs/api.md @@ -37,7 +37,7 @@ | public | getDependentLocales() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)[]
Return an array of parent locales. | | public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | | public | getIdentifier() : string
Returns the locale identifier. | -| public | getLocalizedName() : string
Return the localized version of the locale name. | +| public | getRegionalName() : string
Return the localized version of the locale name. | | public | getName() : string
Return the name of the locale, in English form. | | public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | @@ -110,7 +110,7 @@ | public | getDependentLocales() : [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale)[]
Return an array of parent locales. | | public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | | public | getIdentifier() : string
Returns the locale identifier. | -| public | getLocalizedName() : string
Return the localized version of the locale name. | +| public | getRegionalName() : string
Return the localized version of the locale name. | | public | getName() : string
Return the name of the locale, in English form. | | public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | | protected | loadConfig() : mixed
Loads the config into the class property. | diff --git a/src/Locale.php b/src/Locale.php index 142917e..8cafabf 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -170,10 +170,10 @@ public function getPluralRule(): int * * @return string */ - public function getLocalizedName(): string + public function getRegionalName(): string { - if (isset($this->config['localized_name'])) { - return $this->config['localized_name']; + if (isset($this->config['regional'])) { + return $this->config['regional']; } elseif (isset($this->config['name'])) { return $this->config['name']; } else { diff --git a/src/LocaleInterface.php b/src/LocaleInterface.php index 0a2331d..e1868ce 100644 --- a/src/LocaleInterface.php +++ b/src/LocaleInterface.php @@ -78,5 +78,5 @@ public function getPluralRule(): int; * * @return string */ - public function getLocalizedName(): string; + public function getRegionalName(): string; } diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index 6a7de7c..627419f 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -91,7 +91,7 @@ public function testGetConfig(Locale $locale): void $this->assertSame([ 'name' => 'French', - 'localized_name' => 'Français', + 'regional' => 'Français', 'authors' => [ 'Foo Bar', 'Bar Foo', // Not available in `core` version @@ -130,9 +130,9 @@ public function testGetDetails(Locale $locale): void $this->assertIsString($locale->getName()); $this->assertSame('French', $locale->getName()); - //getLocalizedName - $this->assertIsString($locale->getLocalizedName()); - $this->assertSame('Français', $locale->getLocalizedName()); + //getRegionalName + $this->assertIsString($locale->getRegionalName()); + $this->assertSame('Français', $locale->getRegionalName()); //getDependentLocalesIdentifier $this->assertIsArray($locale->getDependentLocalesIdentifier()); @@ -149,7 +149,7 @@ public function testGetDetails(Locale $locale): void public function testGetLocalizedNameWithNoLocalizedConfig(): void { $locale = new Locale('es_ES'); - $this->assertSame('Spanish', $locale->getLocalizedName()); + $this->assertSame('Spanish', $locale->getRegionalName()); } /** @@ -197,7 +197,7 @@ public function testConstructorWithCustomFile() $this->assertSame([], $locale->getDependentLocalesIdentifier()); $this->assertSame('', $locale->getName()); $this->assertSame(1, $locale->getPluralRule()); - $this->assertSame('de_DE', $locale->getLocalizedName()); + $this->assertSame('de_DE', $locale->getRegionalName()); } /* diff --git a/tests/data/dictionary/locale/en_US/locale.yaml b/tests/data/dictionary/locale/en_US/locale.yaml index a381dce..9ebbaa3 100644 --- a/tests/data/dictionary/locale/en_US/locale.yaml +++ b/tests/data/dictionary/locale/en_US/locale.yaml @@ -1,5 +1,5 @@ name: English -localized_name: English +regional: English authors: - John Appleseed plural_rule: 1 diff --git a/tests/data/dictionary/locale/fr_CA/locale.yaml b/tests/data/dictionary/locale/fr_CA/locale.yaml index b96b841..1aace6a 100644 --- a/tests/data/dictionary/locale/fr_CA/locale.yaml +++ b/tests/data/dictionary/locale/fr_CA/locale.yaml @@ -1,5 +1,5 @@ name: French Canadian -localized_name: Français Canadien +regional: Français Canadien authors: - Foo Bar - Bar Foo diff --git a/tests/data/dictionary/locale/fr_FR/locale.yaml b/tests/data/dictionary/locale/fr_FR/locale.yaml index 962ad21..427fd5b 100644 --- a/tests/data/dictionary/locale/fr_FR/locale.yaml +++ b/tests/data/dictionary/locale/fr_FR/locale.yaml @@ -1,5 +1,5 @@ name: French -localized_name: Français +regional: Français authors: - Foo Bar - Bar Foo diff --git a/tests/data/sprinkles/account/locale/fr_FR/locale.yaml b/tests/data/sprinkles/account/locale/fr_FR/locale.yaml index 962ad21..427fd5b 100644 --- a/tests/data/sprinkles/account/locale/fr_FR/locale.yaml +++ b/tests/data/sprinkles/account/locale/fr_FR/locale.yaml @@ -1,5 +1,5 @@ name: French -localized_name: Français +regional: Français authors: - Foo Bar - Bar Foo diff --git a/tests/data/sprinkles/core/locale/en_US/locale.yaml b/tests/data/sprinkles/core/locale/en_US/locale.yaml index a381dce..9ebbaa3 100644 --- a/tests/data/sprinkles/core/locale/en_US/locale.yaml +++ b/tests/data/sprinkles/core/locale/en_US/locale.yaml @@ -1,5 +1,5 @@ name: English -localized_name: English +regional: English authors: - John Appleseed plural_rule: 1 diff --git a/tests/data/sprinkles/core/locale/fr_FR/locale.yaml b/tests/data/sprinkles/core/locale/fr_FR/locale.yaml index 68f3311..4d7c4ae 100644 --- a/tests/data/sprinkles/core/locale/fr_FR/locale.yaml +++ b/tests/data/sprinkles/core/locale/fr_FR/locale.yaml @@ -1,5 +1,5 @@ name: French -localized_name: Français +regional: Français authors: - Foo Bar plural_rule: 2 diff --git a/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml index b96b841..1aace6a 100644 --- a/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml +++ b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml @@ -1,5 +1,5 @@ name: French Canadian -localized_name: Français Canadien +regional: Français Canadien authors: - Foo Bar - Bar Foo From c3fead56ab009e7660d3863acd96fdef2f9cb0b0 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 10 Dec 2019 20:19:22 -0500 Subject: [PATCH 33/40] Moved doc file --- README.md | 6 + docs/README.md | 324 ++++++++++++++++++++++++++++++++++++++++++++++++- docs/api.md | 321 ------------------------------------------------ 3 files changed, 326 insertions(+), 325 deletions(-) delete mode 100644 docs/api.md diff --git a/README.md b/README.md index a48fe3f..6203d12 100644 --- a/README.md +++ b/README.md @@ -563,3 +563,9 @@ There's a child and no adults in the white Honda Civic 1993 ## [Testing](RUNNING_TESTS.md) ## [API docs](docs/api.md) + +To build docs : + +``` +vendor/bin/phpdoc-md generate src/ > docs/README.md +``` diff --git a/docs/README.md b/docs/README.md index 7428073..c15257a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,321 @@ -# Building doc +## Table of contents + +- [\UserFrosting\I18n\LocaleInterface (interface)](#interface-userfrostingi18nlocaleinterface) +- [\UserFrosting\I18n\Dictionary](#class-userfrostingi18ndictionary) +- [\UserFrosting\I18n\DictionaryInterface (interface)](#interface-userfrostingi18ndictionaryinterface) +- [\UserFrosting\I18n\Translator](#class-userfrostingi18ntranslator) +- [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale) +- [\UserFrosting\I18n\PluralRules\Rule0](#class-userfrostingi18npluralrulesrule0) +- [\UserFrosting\I18n\PluralRules\Rule1](#class-userfrostingi18npluralrulesrule1) +- [\UserFrosting\I18n\PluralRules\Rule3](#class-userfrostingi18npluralrulesrule3) +- [\UserFrosting\I18n\PluralRules\Rule2](#class-userfrostingi18npluralrulesrule2) +- [\UserFrosting\I18n\PluralRules\Rule6](#class-userfrostingi18npluralrulesrule6) +- [\UserFrosting\I18n\PluralRules\Rule7](#class-userfrostingi18npluralrulesrule7) +- [\UserFrosting\I18n\PluralRules\Rule5](#class-userfrostingi18npluralrulesrule5) +- [\UserFrosting\I18n\PluralRules\Rule4](#class-userfrostingi18npluralrulesrule4) +- [\UserFrosting\I18n\PluralRules\Rule14](#class-userfrostingi18npluralrulesrule14) +- [\UserFrosting\I18n\PluralRules\RuleInterface (interface)](#interface-userfrostingi18npluralrulesruleinterface) +- [\UserFrosting\I18n\PluralRules\Rule15](#class-userfrostingi18npluralrulesrule15) +- [\UserFrosting\I18n\PluralRules\Rule12](#class-userfrostingi18npluralrulesrule12) +- [\UserFrosting\I18n\PluralRules\Rule13](#class-userfrostingi18npluralrulesrule13) +- [\UserFrosting\I18n\PluralRules\Rule11](#class-userfrostingi18npluralrulesrule11) +- [\UserFrosting\I18n\PluralRules\Rule10](#class-userfrostingi18npluralrulesrule10) +- [\UserFrosting\I18n\PluralRules\Rule9](#class-userfrostingi18npluralrulesrule9) +- [\UserFrosting\I18n\PluralRules\Rule8](#class-userfrostingi18npluralrulesrule8) + +
+ +### Interface: \UserFrosting\I18n\LocaleInterface + +> Locale interface. + +| Visibility | Function | +|:-----------|:---------| +| public | getAuthors() : string[] The list of authors
Returns the list of authors of the locale. | +| public | getConfig() : \UserFrosting\I18n\(array/\UserFrosting\I18n\string)[]
Return the raw configuration data. | +| public | getConfigFile() : string
Returns defined configuration file. | +| public | getDependentLocales() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)[]
Return an array of parent locales. | +| public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | +| public | getIdentifier() : string
Returns the locale identifier. | +| public | getName() : string
Return the name of the locale, in English form. | +| public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | +| public | getRegionalName() : string
Return the localized version of the locale name. | + +
+ +### Class: \UserFrosting\I18n\Dictionary + +> Locale Dictionary. Load all locale all "Key => translation" data matrix + +| Visibility | Function | +|:-----------|:---------| +| public | __construct([\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface) $locale, \UserFrosting\UniformResourceLocator\ResourceLocatorInterface $locator, \UserFrosting\Support\Repository\Loader\FileRepositoryLoader $fileLoader=null) : void | +| public | getDictionary() : string[] The locale dictionary
Returns all loaded locale Key => Translation data dictionary. Won't load the whole thing twice if already loaded in the class. | +| public | getFileLoader() : \UserFrosting\Support\Repository\Loader\FileRepositoryLoader
Return the file repository loader used to load. | +| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Return the associate locale. | +| public | setUri(\string $uri) : void
Set the locator base URI (default 'locale://'). | +| protected | filterDictionaryFiles(\UserFrosting\UniformResourceLocator\ResourceInterface[] $files) : string[]
Remove config files from locator results and convert ResourceInterface to path/string. | +| protected | getFiles() : \UserFrosting\UniformResourceLocator\ResourceInterface[]
List all files for a given locale using the locator. | +| protected | loadDictionary() : \UserFrosting\I18n\(string/array)[] The locale dictionary
Load the dictionary from file. | + +*This class extends \UserFrosting\Support\Repository\Repository* + +*This class implements \ArrayAccess, \Illuminate\Contracts\Config\Repository, [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)* + +
+ +### Interface: \UserFrosting\I18n\DictionaryInterface + +> Locale Dictionary. Used to return all "Key => translation" data matrix Extend the Config repository to have acess to all the standard `has`, `get`, etc. public methods on the dictionnay array + +| Visibility | Function | +|:-----------|:---------| +| public | getDictionary() : string[] The locale dictionary
Returns all loaded locale Key => Translation data dictionary. | +| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Return the associate locale. | + +*This class implements \Illuminate\Contracts\Config\Repository* + +
+ +### Class: \UserFrosting\I18n\Translator + +> Translator Class. Translate message ids to a message in a specified language. + +| Visibility | Function | +|:-----------|:---------| +| public | __construct([\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface) $dictionary) : void
Create the translator. | +| public | getDictionary() : [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)
Returned the associated dictionary. | +| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Returns the associated locale for the specified dictionary. | +| public | getPluralForm(int/float $number, bool/mixed $forceRule=false) : int The plural-case we need to use for the number plural-rule combination
Determine which plural form we should use. For some languages this is not as simple as for English. | +| public | translate(\string $messageKey, array/array/int $placeholders=array()) : string The translated message.
Translate the given message id into the currently configured language, substituting any placeholders that appear in the translated string. Return the $messageKey if not match is found | +| protected | getMessageFromKey(\string $messageKey, array/int $placeholders) : string The message string
Get the message from key. Go throught all registered language keys avaiable and find the correct one to use, using the placeholders to select the correct plural form. | +| protected | getPluralKey(array $messageArray) : string
Return the plural key from a translation array. If no plural key is defined in the `@PLURAL` instruction of the message array, we fallback to the default one. | +| protected | getPluralMessageKey(array $messageArray, \int $pluralValue) : int/null Returns which key from $messageArray to use
Return the correct plural message form to use. When multiple plural form are available for a message, this method will return the correct oen to use based on the numeric value. | +| protected | getPluralRuleNumber(int/bool $forceRule) : int
Return the correct rule number to use. | +| protected | getPluralValue(array/int $placeholders, \string $pluralKey) : int/null The number, null if not found
Return the plural value, aka the nummber to display, from the placeholder values. | +| protected | parsePlaceHolders(\string $message, array $placeholders) : string The message with replaced placeholders
Parse Placeholder. Replace placeholders in the message with their values from the passed argument. | + +
+ +### Class: \UserFrosting\I18n\Locale + +> Locale Class. Act as a container for a Locale data loaded from filesystem data + +| Visibility | Function | +|:-----------|:---------| +| public | __construct(\string $identifier, \string $configFile=null) : void
Create locale class. | +| public | getAuthors() : string[] The list of authors
Returns the list of authors of the locale. | +| public | getConfig() : \UserFrosting\I18n\(array/\UserFrosting\I18n\string)[]
Return the raw configuration data. | +| public | getConfigFile() : string
Returns defined configuration file. | +| public | getDependentLocales() : [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale)[]
Return an array of parent locales. | +| public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | +| public | getIdentifier() : string
Returns the locale identifier. | +| public | getName() : string
Return the name of the locale, in English form. | +| public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | +| public | getRegionalName() : string
Return the localized version of the locale name. | +| protected | loadConfig() : mixed
Loads the config into the class property. | + +*This class implements [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule0 + +> Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao 1 - everything: 0, 1, 2, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule1 + +> Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan) 1 - 1 2 - everything else: 0, 2, 3, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule3 + +> Families: Baltic (Latvian) 1 - 0 2 - ends in 1, not 11: 1, 21, ... 101, 121, ... 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule2 + +> Families: Romanic (French, Brazilian Portuguese) 1 - 0, 1 2 - everything else: 2, 3, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule6 + +> Families: Baltic (Lithuanian) 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ... 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule7 + +> Families: Slavic (Croatian, Serbian, Russian, Ukrainian) 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule5 + +> Families: Romanic (Romanian) 1 - 1 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ... 3 - everything else: 20, 21, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule4 + +> Families: Celtic (Scottish Gaelic) 1 - is 1 or 11: 1, 11 2 - is 2 or 12: 2, 12 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19 4 - everything else: 0, 20, 21, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule14 + +> Families: Slavic (Macedonian) 1 - ends in 1: 1, 11, 21, ... 2 - ends in 2: 2, 12, 22, ... 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Interface: \UserFrosting\I18n\PluralRules\RuleInterface + +> Interface for Rule Definition. The plural rules are based on a list published by the Mozilla Developer Network & code from phpBB Group + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(int $number) : int The rule
Return the rule to apply. | + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule15 + +> Families: Icelandic 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ... 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule12 + +> Families: Semitic (Arabic). 1 - 1 2 - 2 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ... 4 - ends in 11-99: 11, ... 99, 111, 112, ... 5 - everything else: 100, 101, 102, 200, 201, 202, ... 6 - 0 + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule13 + +> Families: Semitic (Maltese) 1 - 1 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ... 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ... 4 - everything else: 20, 21, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule11 + +> Families: Celtic (Irish Gaeilge) 1 - 1 2 - 2 3 - is 3-6: 3, 4, 5, 6 4 - is 7-10: 7, 8, 9, 10 5 - everything else: 0, 11, 12, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule10 + +> Families: Slavic (Slovenian, Sorbian) 1 - ends in 01: 1, 101, 201, ... 2 - ends in 02: 2, 102, 202, ... 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ... 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule9 + +> Families: Slavic (Polish) 1 - 1 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ... 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* + +
+ +### Class: \UserFrosting\I18n\PluralRules\Rule8 + +> Families: Slavic (Slovak, Czech) 1 - 1 2 - 2, 3, 4 3 - everything else: 0, 5, 6, 7, ... + +| Visibility | Function | +|:-----------|:---------| +| public static | getRule(mixed $number) : mixed | + +*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* -``` -vendor/bin/phpdoc-md generate src/ > docs/api.md -``` diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 0484159..0000000 --- a/docs/api.md +++ /dev/null @@ -1,321 +0,0 @@ -## Table of contents - -- [\UserFrosting\I18n\LocaleInterface (interface)](#interface-userfrostingi18nlocaleinterface) -- [\UserFrosting\I18n\Dictionary](#class-userfrostingi18ndictionary) -- [\UserFrosting\I18n\DictionaryInterface (interface)](#interface-userfrostingi18ndictionaryinterface) -- [\UserFrosting\I18n\Translator](#class-userfrostingi18ntranslator) -- [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale) -- [\UserFrosting\I18n\PluralRules\Rule0](#class-userfrostingi18npluralrulesrule0) -- [\UserFrosting\I18n\PluralRules\Rule1](#class-userfrostingi18npluralrulesrule1) -- [\UserFrosting\I18n\PluralRules\Rule3](#class-userfrostingi18npluralrulesrule3) -- [\UserFrosting\I18n\PluralRules\Rule2](#class-userfrostingi18npluralrulesrule2) -- [\UserFrosting\I18n\PluralRules\Rule6](#class-userfrostingi18npluralrulesrule6) -- [\UserFrosting\I18n\PluralRules\Rule7](#class-userfrostingi18npluralrulesrule7) -- [\UserFrosting\I18n\PluralRules\Rule5](#class-userfrostingi18npluralrulesrule5) -- [\UserFrosting\I18n\PluralRules\Rule4](#class-userfrostingi18npluralrulesrule4) -- [\UserFrosting\I18n\PluralRules\Rule14](#class-userfrostingi18npluralrulesrule14) -- [\UserFrosting\I18n\PluralRules\RuleInterface (interface)](#interface-userfrostingi18npluralrulesruleinterface) -- [\UserFrosting\I18n\PluralRules\Rule15](#class-userfrostingi18npluralrulesrule15) -- [\UserFrosting\I18n\PluralRules\Rule12](#class-userfrostingi18npluralrulesrule12) -- [\UserFrosting\I18n\PluralRules\Rule13](#class-userfrostingi18npluralrulesrule13) -- [\UserFrosting\I18n\PluralRules\Rule11](#class-userfrostingi18npluralrulesrule11) -- [\UserFrosting\I18n\PluralRules\Rule10](#class-userfrostingi18npluralrulesrule10) -- [\UserFrosting\I18n\PluralRules\Rule9](#class-userfrostingi18npluralrulesrule9) -- [\UserFrosting\I18n\PluralRules\Rule8](#class-userfrostingi18npluralrulesrule8) - -
- -### Interface: \UserFrosting\I18n\LocaleInterface - -> Locale interface. - -| Visibility | Function | -|:-----------|:---------| -| public | getAuthors() : string[] The list of authors
Returns the list of authors of the locale. | -| public | getConfig() : \UserFrosting\I18n\(array/\UserFrosting\I18n\string)[]
Return the raw configuration data. | -| public | getConfigFile() : string
Returns defined configuration file. | -| public | getDependentLocales() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)[]
Return an array of parent locales. | -| public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | -| public | getIdentifier() : string
Returns the locale identifier. | -| public | getRegionalName() : string
Return the localized version of the locale name. | -| public | getName() : string
Return the name of the locale, in English form. | -| public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | - -
- -### Class: \UserFrosting\I18n\Dictionary - -> Locale Dictionary. Load all locale all "Key => translation" data matrix - -| Visibility | Function | -|:-----------|:---------| -| public | __construct([\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface) $locale, \UserFrosting\UniformResourceLocator\ResourceLocatorInterface $locator, \UserFrosting\Support\Repository\Loader\FileRepositoryLoader $fileLoader=null) : void | -| public | getDictionary() : string[] The locale dictionary
Returns all loaded locale Key => Translation data dictionary. Won't load the whole thing twice if already loaded in the class. | -| public | getFileLoader() : \UserFrosting\Support\Repository\Loader\FileRepositoryLoader
Return the file repository loader used to load. | -| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Return the associate locale. | -| public | setUri(\string $uri) : void
Set the locator base URI (default 'locale://'). | -| protected | filterDictionaryFiles(\UserFrosting\UniformResourceLocator\ResourceInterface[] $files) : string[]
Remove config files from locator results and convert ResourceInterface to path/string. | -| protected | getFiles() : \UserFrosting\UniformResourceLocator\ResourceInterface[]
List all files for a given locale using the locator. | -| protected | loadDictionary() : \UserFrosting\I18n\(string/array)[] The locale dictionary
Load the dictionary from file. | - -*This class extends \UserFrosting\Support\Repository\Repository* - -*This class implements \ArrayAccess, \Illuminate\Contracts\Config\Repository, [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)* - -
- -### Interface: \UserFrosting\I18n\DictionaryInterface - -> Locale Dictionary. Used to return all "Key => translation" data matrix Extend the Config repository to have acess to all the standard `has`, `get`, etc. public methods on the dictionnay array - -| Visibility | Function | -|:-----------|:---------| -| public | getDictionary() : string[] The locale dictionary
Returns all loaded locale Key => Translation data dictionary. | -| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Return the associate locale. | - -*This class implements \Illuminate\Contracts\Config\Repository* - -
- -### Class: \UserFrosting\I18n\Translator - -> Translator Class. Translate message ids to a message in a specified language. - -| Visibility | Function | -|:-----------|:---------| -| public | __construct([\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface) $dictionary) : void
Create the translator. | -| public | getDictionary() : [\UserFrosting\I18n\DictionaryInterface](#interface-userfrostingi18ndictionaryinterface)
Returned the associated dictionary. | -| public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Returns the associated locale for the specified dictionary. | -| public | getPluralForm(int/float $number, bool/mixed $forceRule=false) : int The plural-case we need to use for the number plural-rule combination
Determine which plural form we should use. For some languages this is not as simple as for English. | -| public | translate(\string $messageKey, array/array/int $placeholders=array()) : string The translated message.
Translate the given message id into the currently configured language, substituting any placeholders that appear in the translated string. Return the $messageKey if not match is found | -| protected | getMessageFromKey(\string $messageKey, array/int $placeholders) : string The message string
Get the message from key. Go throught all registered language keys avaiable and find the correct one to use, using the placeholders to select the correct plural form. | -| protected | getPluralKey(array $messageArray) : string
Return the plural key from a translation array. If no plural key is defined in the `@PLURAL` instruction of the message array, we fallback to the default one. | -| protected | getPluralMessageKey(array $messageArray, \int $pluralValue) : int/null Returns which key from $messageArray to use
Return the correct plural message form to use. When multiple plural form are available for a message, this method will return the correct oen to use based on the numeric value. | -| protected | getPluralRuleNumber(int/bool $forceRule) : int
Return the correct rule number to use. | -| protected | getPluralValue(array/int $placeholders, \string $pluralKey) : int/null The number, null if not found
Return the plural value, aka the nummber to display, from the placeholder values. | -| protected | parsePlaceHolders(\string $message, array $placeholders) : string The message with replaced placeholders
Parse Placeholder. Replace placeholders in the message with their values from the passed argument. | - -
- -### Class: \UserFrosting\I18n\Locale - -> Locale Class. Act as a container for a Locale data loaded from filesystem data - -| Visibility | Function | -|:-----------|:---------| -| public | __construct(\string $identifier, \string $configFile=null) : void
Create locale class. | -| public | getAuthors() : string[] The list of authors
Returns the list of authors of the locale. | -| public | getConfig() : \UserFrosting\I18n\(array/\UserFrosting\I18n\string)[]
Return the raw configuration data. | -| public | getConfigFile() : string
Returns defined configuration file. | -| public | getDependentLocales() : [\UserFrosting\I18n\Locale](#class-userfrostingi18nlocale)[]
Return an array of parent locales. | -| public | getDependentLocalesIdentifier() : string[]
Return a list of parent locale identifier (eg. [fr_FR, en_US]). | -| public | getIdentifier() : string
Returns the locale identifier. | -| public | getRegionalName() : string
Return the localized version of the locale name. | -| public | getName() : string
Return the name of the locale, in English form. | -| public | getPluralRule() : int
Return the number representing the plural rule to use for this locale. | -| protected | loadConfig() : mixed
Loads the config into the class property. | - -*This class implements [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule0 - -> Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao 1 - everything: 0, 1, 2, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule1 - -> Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan) 1 - 1 2 - everything else: 0, 2, 3, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule3 - -> Families: Baltic (Latvian) 1 - 0 2 - ends in 1, not 11: 1, 21, ... 101, 121, ... 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule2 - -> Families: Romanic (French, Brazilian Portuguese) 1 - 0, 1 2 - everything else: 2, 3, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule6 - -> Families: Baltic (Lithuanian) 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ... 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule7 - -> Families: Slavic (Croatian, Serbian, Russian, Ukrainian) 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule5 - -> Families: Romanic (Romanian) 1 - 1 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ... 3 - everything else: 20, 21, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule4 - -> Families: Celtic (Scottish Gaelic) 1 - is 1 or 11: 1, 11 2 - is 2 or 12: 2, 12 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19 4 - everything else: 0, 20, 21, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule14 - -> Families: Slavic (Macedonian) 1 - ends in 1: 1, 11, 21, ... 2 - ends in 2: 2, 12, 22, ... 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Interface: \UserFrosting\I18n\PluralRules\RuleInterface - -> Interface for Rule Definition. The plural rules are based on a list published by the Mozilla Developer Network & code from phpBB Group - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(int $number) : int The rule
Return the rule to apply. | - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule15 - -> Families: Icelandic 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ... 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule12 - -> Families: Semitic (Arabic). 1 - 1 2 - 2 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ... 4 - ends in 11-99: 11, ... 99, 111, 112, ... 5 - everything else: 100, 101, 102, 200, 201, 202, ... 6 - 0 - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule13 - -> Families: Semitic (Maltese) 1 - 1 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ... 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ... 4 - everything else: 20, 21, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule11 - -> Families: Celtic (Irish Gaeilge) 1 - 1 2 - 2 3 - is 3-6: 3, 4, 5, 6 4 - is 7-10: 7, 8, 9, 10 5 - everything else: 0, 11, 12, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule10 - -> Families: Slavic (Slovenian, Sorbian) 1 - ends in 01: 1, 101, 201, ... 2 - ends in 02: 2, 102, 202, ... 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ... 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule9 - -> Families: Slavic (Polish) 1 - 1 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ... 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - -
- -### Class: \UserFrosting\I18n\PluralRules\Rule8 - -> Families: Slavic (Slovak, Czech) 1 - 1 2 - 2, 3, 4 3 - everything else: 0, 5, 6, 7, ... - -| Visibility | Function | -|:-----------|:---------| -| public static | getRule(mixed $number) : mixed | - -*This class implements [\UserFrosting\I18n\PluralRules\RuleInterface](#interface-userfrostingi18npluralrulesruleinterface)* - From 5061db62e6699b10f3bb5883bd208a68e50c0e05 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sun, 12 Jan 2020 22:13:24 -0500 Subject: [PATCH 34/40] Added `getFlattenDictionary` --- docs/README.md | 1 + src/Dictionary.php | 11 +++++ tests/DictionaryTest.php | 46 +++++++++++++++++---- tests/data/dictionary/locale/en_US/test.php | 7 ++-- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/docs/README.md b/docs/README.md index c15257a..35801a0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,6 +52,7 @@ | public | __construct([\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface) $locale, \UserFrosting\UniformResourceLocator\ResourceLocatorInterface $locator, \UserFrosting\Support\Repository\Loader\FileRepositoryLoader $fileLoader=null) : void | | public | getDictionary() : string[] The locale dictionary
Returns all loaded locale Key => Translation data dictionary. Won't load the whole thing twice if already loaded in the class. | | public | getFileLoader() : \UserFrosting\Support\Repository\Loader\FileRepositoryLoader
Return the file repository loader used to load. | +| public | getFlattenDictionary() : string[]
Return the dictionnary as a flatten array, using dot notation | | public | getLocale() : [\UserFrosting\I18n\LocaleInterface](#interface-userfrostingi18nlocaleinterface)
Return the associate locale. | | public | setUri(\string $uri) : void
Set the locator base URI (default 'locale://'). | | protected | filterDictionaryFiles(\UserFrosting\UniformResourceLocator\ResourceInterface[] $files) : string[]
Remove config files from locator results and convert ResourceInterface to path/string. | diff --git a/src/Dictionary.php b/src/Dictionary.php index eea18ff..f28484e 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -10,6 +10,7 @@ namespace UserFrosting\I18n; +use Illuminate\Support\Arr; use UserFrosting\Support\Repository\Loader\ArrayFileLoader; use UserFrosting\Support\Repository\Loader\FileRepositoryLoader; use UserFrosting\Support\Repository\Repository; @@ -76,6 +77,16 @@ public function getDictionary(): array return $this->items; } + /** + * Return the dictionnary as a flatten array, using dot notation + * + * @return string[] + */ + public function getFlattenDictionary(): array + { + return Arr::dot($this->getDictionary()); + } + /** * Set the locator base URI (default 'locale://'). * diff --git a/tests/DictionaryTest.php b/tests/DictionaryTest.php index 81c805e..318d12e 100644 --- a/tests/DictionaryTest.php +++ b/tests/DictionaryTest.php @@ -561,10 +561,11 @@ public function testGetDictionary_withRealLocale_withDependentLocaleDataOnBoth() $expectedResult = [ 'Foo' => 'Bar', 'test' => [ - 'aaa' => 'AAA', - 'ccc' => '', - 'ddd' => 'DDD', - 'bbb' => 'BBB', + 'aaa' => 'AAA', + 'ccc' => '', + 'ddd' => 'DDD', + 'bbb' => 'BBB', + '@TRANSLATION' => 'Test', ], 'Bar' => 'Foo', ]; @@ -579,6 +580,34 @@ public function testGetDictionary_withRealLocale_withDependentLocaleDataOnBoth() $this->assertEquals($expectedResult, $data); } + /** + * @ depends testConstructor + */ + public function testGetDictionary_forFlat(): void + { + // Set expectations + // fr_FR depends on en_US. So FR data will be loaded over EN data + // Replicate testGetDictionary_withDependentLocaleDataOnBoth result + $expectedResult = [ + 'Foo' => 'Bar', + 'test.@TRANSLATION' => 'Test', + 'test.aaa' => 'AAA', + 'test.ccc' => '', + 'test.ddd' => 'DDD', + 'test.bbb' => 'BBB', + 'Bar' => 'Foo', + ]; + + // Get dictionary + $locale = new Locale('fr_FR'); + $dictionary = new Dictionary($locale, $this->locator); + $data = $dictionary->getFlattenDictionary(); + + // Perform assertions + $this->assertIsArray($data); + $this->assertEquals($expectedResult, $data); + } + public function testGetDictionary_withRealLocale_withThirdDependentLocale(): void { // Set expectations @@ -588,10 +617,11 @@ public function testGetDictionary_withRealLocale_withThirdDependentLocale(): voi $expectedResult = [ 'Foo' => 'Tabarnak', 'test' => [ - 'aaa' => 'AAA', - 'ccc' => '', - 'ddd' => 'DDD', - 'bbb' => 'BBB', + 'aaa' => 'AAA', + 'ccc' => '', + 'ddd' => 'DDD', + 'bbb' => 'BBB', + '@TRANSLATION' => 'Test', ], 'Bar' => 'Foo', ]; diff --git a/tests/data/dictionary/locale/en_US/test.php b/tests/data/dictionary/locale/en_US/test.php index a0a7628..c73856c 100644 --- a/tests/data/dictionary/locale/en_US/test.php +++ b/tests/data/dictionary/locale/en_US/test.php @@ -11,8 +11,9 @@ return [ 'Bar' => 'Foo', 'test' => [ - 'bbb' => 'BBB', - 'ccc' => 'CCC', // Overwriten by "" - 'ddd' => '', //Overwriten by "DDD" + '@TRANSLATION' => 'Test', + 'bbb' => 'BBB', + 'ccc' => 'CCC', // Overwriten by "" + 'ddd' => '', //Overwriten by "DDD" ], ]; From 7c5a9ec4b55d7930273e3d8cc22131a37b5bd218 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Sun, 12 Jan 2020 22:14:44 -0500 Subject: [PATCH 35/40] Fix Style --- src/Dictionary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dictionary.php b/src/Dictionary.php index f28484e..b1327dd 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -78,7 +78,7 @@ public function getDictionary(): array } /** - * Return the dictionnary as a flatten array, using dot notation + * Return the dictionnary as a flatten array, using dot notation. * * @return string[] */ From 06ca85cf82aa428a569540b565c43044572994da Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Thu, 16 Jan 2020 21:27:15 -0500 Subject: [PATCH 36/40] Added Compare helper class --- src/Compare.php | 116 ++++++++++++++++++++++++++ src/Dictionary.php | 15 +--- src/DictionaryInterface.php | 9 +- tests/CompareTest.php | 160 ++++++++++++++++++++++++++++++++++++ 4 files changed, 288 insertions(+), 12 deletions(-) create mode 100644 src/Compare.php create mode 100644 tests/CompareTest.php diff --git a/src/Compare.php b/src/Compare.php new file mode 100644 index 0000000..cb60c34 --- /dev/null +++ b/src/Compare.php @@ -0,0 +1,116 @@ +getFlattenDictionary(), $rightDictionary->getFlattenDictionary()); + + if ($undot) { + return self::undot($diff); + } + + return $diff; + } + + /** + * Compares keys from left dictionary against right dictionary. + * Returns a list of keys present in the left dictory, but not found in the right one. + * Can be used to sync both dictionary content. + * + * @param DictionaryInterface $leftDictionary + * @param DictionaryInterface $rightDictionary + * + * @return string[] List of keys + */ + public static function dictionariesKeys(DictionaryInterface $leftDictionary, DictionaryInterface $rightDictionary): array + { + $diff = array_diff_key($leftDictionary->getFlattenDictionary(), $rightDictionary->getFlattenDictionary()); + + return array_keys($diff); + } + + /** + * Compares values from left dictionary against right dictionary to find same values. + * Returns a list of values which are the same in both dictionaries. + * Can be used to find all values in the right directory that might not have been translated compared to the left one. + * For example, when comparing french and english dictionaries, this can be used to return all English values present in the French dictionary. + * + * @param DictionaryInterface $leftDictionary + * @param DictionaryInterface $rightDictionary + * @param bool $undot If true, return the dot notation associative array. If false, returns the normal multidimensional associative array [Default: false] + * + * @return string[] + */ + public static function dictionariesValues(DictionaryInterface $leftDictionary, DictionaryInterface $rightDictionary, bool $undot = false): array + { + $diff = array_intersect($leftDictionary->getFlattenDictionary(), $rightDictionary->getFlattenDictionary()); + + if ($undot) { + return self::undot($diff); + } + + return $diff; + } + + /** + * Returns all keys for which the value is empty. + * Can be used to find all keys that needs to be translated in the dictionary. + * + * @param DictionaryInterface $dictionary + * + * @return string[] List of keys + */ + public static function dictionariesEmptyValues(DictionaryInterface $dictionary): array + { + $diff = array_filter($dictionary->getFlattenDictionary(), function ($value) { + return $value == ''; + }); + + return array_keys($diff); + } + + /** + * Transfer dot notation associative array back into multidimentional associative array. + * + * @param mixed[] $array + * + * @return mixed[] + */ + protected static function undot(array $array): array + { + $result = []; + foreach ($array as $key => $value) { + Arr::set($result, $key, $value); + } + + return $result; + } +} diff --git a/src/Dictionary.php b/src/Dictionary.php index b1327dd..ccbedd1 100644 --- a/src/Dictionary.php +++ b/src/Dictionary.php @@ -46,7 +46,7 @@ class Dictionary extends Repository implements DictionaryInterface protected $fileLoader; /** - * @var array Locale "Key => translation" data matrix cache + * @var (string|array)[] Locale "Key => translation" data matrix cache */ protected $items = []; @@ -63,10 +63,7 @@ public function __construct(LocaleInterface $locale, ResourceLocatorInterface $l } /** - * Returns all loaded locale Key => Translation data dictionary. - * Won't load the whole thing twice if already loaded in the class. - * - * @return string[] The locale dictionary + * {@inheritdoc} */ public function getDictionary(): array { @@ -78,9 +75,7 @@ public function getDictionary(): array } /** - * Return the dictionnary as a flatten array, using dot notation. - * - * @return string[] + * {@inheritdoc} */ public function getFlattenDictionary(): array { @@ -98,9 +93,7 @@ public function setUri(string $uri): void } /** - * Return the associate locale. - * - * @return LocaleInterface + * {@inheritdoc} */ public function getLocale(): LocaleInterface { diff --git a/src/DictionaryInterface.php b/src/DictionaryInterface.php index 9729392..3947e30 100644 --- a/src/DictionaryInterface.php +++ b/src/DictionaryInterface.php @@ -26,7 +26,7 @@ interface DictionaryInterface extends Repository /** * Returns all loaded locale Key => Translation data dictionary. * - * @return string[] The locale dictionary + * @return (string|array)[] The locale dictionary */ public function getDictionary(): array; @@ -36,4 +36,11 @@ public function getDictionary(): array; * @return LocaleInterface */ public function getLocale(): LocaleInterface; + + /** + * Return the dictionnary as a flatten array, using dot notation. + * + * @return string[] + */ + public function getFlattenDictionary(): array; } diff --git a/tests/CompareTest.php b/tests/CompareTest.php new file mode 100644 index 0000000..7ed184a --- /dev/null +++ b/tests/CompareTest.php @@ -0,0 +1,160 @@ +left = Mockery::mock(DictionaryInterface::class); + $this->left->shouldReceive('getFlattenDictionary')->andReturn([ + 'Foo' => 'Bar', + 'test.@TRANSLATION' => 'Test', + 'test.aaa' => 'AAA', + 'test.bbb' => 'BBB', + 'test.ccc' => 'CCC', + 'test.ddd' => 'DDD', + 'Bar' => 'Foo', + ]); + + $this->right = Mockery::mock(DictionaryInterface::class); + $this->right->shouldReceive('getFlattenDictionary')->andReturn([ + 'Foo' => 'Bar', + 'test.@TRANSLATION' => 'Test', + 'test.aaa' => 'AAA', + 'test.ccc' => '', + 'test.bbb' => 'BBB', + ]); + } + + /** + * {@inheritdoc} + */ + public function tearDown() + { + Mockery::close(); + } + + public function testDictionaries(): void + { + // Compare flatten dictionaries + // L -> R + $this->assertSame([ + 'test.ccc' => 'CCC', + 'test.ddd' => 'DDD', + 'Bar' => 'Foo', + ], Compare::dictionaries($this->left, $this->right)); + + // R -> L + $this->assertSame([ + 'test.ccc' => '', + ], Compare::dictionaries($this->right, $this->left)); + + // Compare direct dictionaries + // L -> R + $this->assertSame([ + 'test' => [ + 'ccc' => 'CCC', + 'ddd' => 'DDD', + ], + 'Bar' => 'Foo', + ], Compare::dictionaries($this->left, $this->right, true)); + + // R -> L + $this->assertSame([ + 'test' => [ + 'ccc' => '', + ], + ], Compare::dictionaries($this->right, $this->left, true)); + } + + public function testDictionariesKeys(): void + { + // L -> R + $this->assertSame([ + 'test.ddd', + 'Bar', + ], Compare::dictionariesKeys($this->left, $this->right)); + + // R -> L + $this->assertSame([], Compare::dictionariesKeys($this->right, $this->left)); + } + + public function testDictionariesValues(): void + { + // Compare flatten dictionaries + // L -> R + $this->assertSame([ + 'Foo' => 'Bar', + 'test.@TRANSLATION' => 'Test', + 'test.aaa' => 'AAA', + 'test.bbb' => 'BBB', + ], Compare::dictionariesValues($this->left, $this->right)); + + // R -> L + $this->assertSame([ + 'Foo' => 'Bar', + 'test.@TRANSLATION' => 'Test', + 'test.aaa' => 'AAA', + 'test.bbb' => 'BBB', + ], Compare::dictionariesValues($this->right, $this->left)); + + // Compare direct dictionaries + // L -> R + $this->assertSame([ + 'Foo' => 'Bar', + 'test' => [ + '@TRANSLATION' => 'Test', + 'aaa' => 'AAA', + 'bbb' => 'BBB', + ], + ], Compare::dictionariesValues($this->left, $this->right, true)); + + // R -> L + $this->assertSame([ + 'Foo' => 'Bar', + 'test' => [ + '@TRANSLATION' => 'Test', + 'aaa' => 'AAA', + 'bbb' => 'BBB', + ], + ], Compare::dictionariesValues($this->right, $this->left, true)); + } + + public function testDictionariesEmptyValues(): void + { + // Left + $this->assertSame([], Compare::dictionariesEmptyValues($this->left)); + + // Right + $this->assertSame(['test.ccc'], Compare::dictionariesEmptyValues($this->right)); + } +} From 4cf6ffe429d8d8fdcbce40dece78d8e60c3ab428 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Thu, 16 Jan 2020 21:39:23 -0500 Subject: [PATCH 37/40] Removed PHP 7.4 from allowed failure --- .travis.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 51b5702..a6d7fef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,15 @@ sudo: false -dist: trusty +dist: xenial language: php php: - 7.1 - 7.2 - 7.3 - - 7.4snapshot + - 7.4 matrix: fast_finish: true - - # Remove before UF 4.4.0 official release, and after 7.4 release ! - allow_failures: - - php: 7.4snapshot cache: directories: From ccea4cd6243e1ac26e254805788f43c115379c75 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Fri, 17 Jan 2020 20:22:44 -0500 Subject: [PATCH 38/40] Fix edge case in `dictionariesValues` --- src/Compare.php | 2 +- tests/CompareTest.php | 33 ++++++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Compare.php b/src/Compare.php index cb60c34..7c65174 100644 --- a/src/Compare.php +++ b/src/Compare.php @@ -71,7 +71,7 @@ public static function dictionariesKeys(DictionaryInterface $leftDictionary, Dic */ public static function dictionariesValues(DictionaryInterface $leftDictionary, DictionaryInterface $rightDictionary, bool $undot = false): array { - $diff = array_intersect($leftDictionary->getFlattenDictionary(), $rightDictionary->getFlattenDictionary()); + $diff = array_intersect_assoc($leftDictionary->getFlattenDictionary(), $rightDictionary->getFlattenDictionary()); if ($undot) { return self::undot($diff); diff --git a/tests/CompareTest.php b/tests/CompareTest.php index 7ed184a..de39b3e 100644 --- a/tests/CompareTest.php +++ b/tests/CompareTest.php @@ -43,15 +43,18 @@ public function setup() 'test.ccc' => 'CCC', 'test.ddd' => 'DDD', 'Bar' => 'Foo', + 'color' => 'Color', ]); $this->right = Mockery::mock(DictionaryInterface::class); $this->right->shouldReceive('getFlattenDictionary')->andReturn([ - 'Foo' => 'Bar', - 'test.@TRANSLATION' => 'Test', - 'test.aaa' => 'AAA', - 'test.ccc' => '', - 'test.bbb' => 'BBB', + 'Foo' => 'Bar', + 'test.@TRANSLATION' => 'Test', + 'test.aaa' => 'AAA', + 'test.ccc' => '', + 'test.bbb' => 'BBB', + 'color.@TRANSLATION' => 'Color', + 'color.red' => 'Red', ]); } @@ -71,21 +74,25 @@ public function testDictionaries(): void 'test.ccc' => 'CCC', 'test.ddd' => 'DDD', 'Bar' => 'Foo', + 'color' => 'Color', ], Compare::dictionaries($this->left, $this->right)); // R -> L $this->assertSame([ - 'test.ccc' => '', + 'test.ccc' => '', + 'color.@TRANSLATION' => 'Color', + 'color.red' => 'Red', ], Compare::dictionaries($this->right, $this->left)); // Compare direct dictionaries // L -> R $this->assertSame([ - 'test' => [ + 'test' => [ 'ccc' => 'CCC', 'ddd' => 'DDD', ], - 'Bar' => 'Foo', + 'Bar' => 'Foo', + 'color' => 'Color', ], Compare::dictionaries($this->left, $this->right, true)); // R -> L @@ -93,6 +100,10 @@ public function testDictionaries(): void 'test' => [ 'ccc' => '', ], + 'color' => [ + '@TRANSLATION' => 'Color', + 'red' => 'Red', + ], ], Compare::dictionaries($this->right, $this->left, true)); } @@ -102,10 +113,14 @@ public function testDictionariesKeys(): void $this->assertSame([ 'test.ddd', 'Bar', + 'color', ], Compare::dictionariesKeys($this->left, $this->right)); // R -> L - $this->assertSame([], Compare::dictionariesKeys($this->right, $this->left)); + $this->assertSame([ + 'color.@TRANSLATION', + 'color.red', + ], Compare::dictionariesKeys($this->right, $this->left)); } public function testDictionariesValues(): void From 78fd865bf6cfef30139f3c6b4cf2ea5425b802d2 Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Mon, 17 Feb 2020 21:05:17 -0500 Subject: [PATCH 39/40] Locale inherit config from parent. Ref #7 --- src/Locale.php | 13 ++-- tests/LocaleTest.php | 63 +++++++++++++++---- .../sprinkles/fr_CA/locale/fr_CA/locale.yaml | 4 -- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/Locale.php b/src/Locale.php index 8cafabf..b2c4bd1 100644 --- a/src/Locale.php +++ b/src/Locale.php @@ -32,7 +32,7 @@ class Locale implements LocaleInterface protected $configFile = ''; /** - * @var array Locale config data, loaded from the locale YAML file + * @var string[] Locale config data, loaded from the locale YAML file */ protected $config; @@ -60,6 +60,11 @@ protected function loadConfig(): void { $loader = new YamlFileLoader($this->configFile); $this->config = $loader->load(false); + + // Load nested locales config + foreach ($this->getDependentLocales() as $locale) { + $this->config = array_merge($locale->getConfig(), $this->config); + } } /** @@ -72,7 +77,7 @@ public function getAuthors(): array if (!isset($this->config['authors'])) { return []; } else { - return $this->config['authors']; + return (array) $this->config['authors']; } } @@ -99,7 +104,7 @@ public function getIdentifier(): string /** * Return the raw configuration data. * - * @return (array|string)[] + * @return string[] */ public function getConfig(): array { @@ -159,7 +164,7 @@ public function getName(): string public function getPluralRule(): int { if (isset($this->config['plural_rule'])) { - return $this->config['plural_rule']; + return (int) $this->config['plural_rule']; } else { return 1; } diff --git a/tests/LocaleTest.php b/tests/LocaleTest.php index 627419f..b4854b3 100644 --- a/tests/LocaleTest.php +++ b/tests/LocaleTest.php @@ -18,8 +18,10 @@ class LocaleTest extends TestCase { + /** @var string */ protected $basePath; + /** @var ResourceLocator */ protected $locator; public function setUp() @@ -32,7 +34,7 @@ public function setUp() // Add them one at a time to simulate how they are added in SprinkleManager $this->locator->registerLocation('core'); $this->locator->registerLocation('account'); - $this->locator->registerLocation('admin'); + $this->locator->registerLocation('fr_CA'); } public function testConstructor(): Locale @@ -137,10 +139,6 @@ public function testGetDetails(Locale $locale): void //getDependentLocalesIdentifier $this->assertIsArray($locale->getDependentLocalesIdentifier()); $this->assertSame(['en_US'], $locale->getDependentLocalesIdentifier()); - - //getPluralRule - $this->assertIsInt($locale->getPluralRule()); - $this->assertSame(2, $locale->getPluralRule()); } /** @@ -173,6 +171,41 @@ public function testGetPluralRuleWithNoRule(): void $this->assertSame(1, $locale->getPluralRule()); } + /** + * @depends testGetDetails + * @depends testGetAuthors + */ + public function testGetDetailsWithInheritance(): void + { + $locale = new Locale('fr_CA'); + + //getName + $this->assertIsString($locale->getName()); + $this->assertSame('French Canadian', $locale->getName()); + + //getRegionalName + $this->assertIsString($locale->getRegionalName()); + $this->assertSame('Français Canadien', $locale->getRegionalName()); + + //getDependentLocalesIdentifier + $this->assertIsArray($locale->getDependentLocalesIdentifier()); + $this->assertSame(['fr_FR'], $locale->getDependentLocalesIdentifier()); + + //getAuthors + $this->assertSame(['Foo Bar', 'Bar Foo'], $locale->getAuthors()); + } + + /** + * @depends testConstructorWithNotPath + * @depends testGetPluralRule + */ + public function testGetPluralRuleWithInheritance(): void + { + $locale = new Locale('fr_CA'); + $this->assertIsInt($locale->getPluralRule()); + $this->assertSame(2, $locale->getPluralRule()); + } + /** * @depends testConstructor * @depends testGetDetails @@ -184,7 +217,19 @@ public function testGetDependentLocales(Locale $locale): void $this->assertInstanceOf(LocaleInterface::class, $result[0]); } - public function testConstructorWithCustomFile() + /** + * @depends testGetDependentLocales + */ + public function testGetDependentLocalesWithNullParent(): void + { + $locale = new Locale('es_ES'); + + $result = $locale->getDependentLocales(); + $this->assertIsArray($result); + $this->assertEmpty($result); + } + + public function testConstructorWithCustomFile(): void { $locale = new Locale('de_DE', 'locale://de_DE/foo.yaml'); $this->assertInstanceOf(LocaleInterface::class, $locale); @@ -199,10 +244,4 @@ public function testConstructorWithCustomFile() $this->assertSame(1, $locale->getPluralRule()); $this->assertSame('de_DE', $locale->getRegionalName()); } - - /* - TODO : - - fr_CA - - Null Parent - */ } diff --git a/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml index 1aace6a..f1f901b 100644 --- a/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml +++ b/tests/data/sprinkles/fr_CA/locale/fr_CA/locale.yaml @@ -1,8 +1,4 @@ name: French Canadian regional: Français Canadien -authors: - - Foo Bar - - Bar Foo -plural_rule: 2 parents: - fr_FR From 249080fc806ee7b2acff850ed6ecf0ad4795edac Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Tue, 17 Mar 2020 20:52:23 -0400 Subject: [PATCH 40/40] Update dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 92255ad..55ec433 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.1", "twig/twig": "^2.11", - "userfrosting/support": "~4.3.1" + "userfrosting/support": "~4.4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.13",