Skip to content
This repository has been archived by the owner on Apr 28, 2024. It is now read-only.

Commit

Permalink
Release 4.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
lcharette authored Mar 18, 2020
2 parents c964f2d + 249080f commit 9f2e255
Show file tree
Hide file tree
Showing 43 changed files with 2,687 additions and 366 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ composer.lock
*.komodoproject
.php_cs.cache
_meta
.serenata
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
sudo: false
dist: trusty
dist: xenial
language: php

php:
- 7.1
- 7.2
- 7.3
- 7.4

matrix:
fast_finish: true

cache:
directories:
- $HOME/.composer/cache
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
216 changes: 178 additions & 38 deletions README.md

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
"require": {
"php": ">=7.1",
"twig/twig": "^2.11",
"userfrosting/support": "~4.3.0"
"userfrosting/support": "~4.4.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.13",
"phpunit/phpunit": "^7.5"
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^7.5",
"victorjonsson/markdowndocs": "^1.3"
},
"autoload": {
"psr-4": {
Expand Down
322 changes: 322 additions & 0 deletions docs/README.md

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions src/Compare.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

/*
* UserFrosting i18n (http://www.userfrosting.com)
*
* @link https://github.com/userfrosting/i18n
* @copyright Copyright (c) 2013-2019 Alexander Weissman, Louis Charette
* @license https://github.com/userfrosting/i18n/blob/master/LICENSE.md (MIT License)
*/

namespace UserFrosting\I18n;

use Illuminate\Support\Arr;

/**
* Helper class to compare two dictionaries.
* Can be used to determine which keys/values are missing from a dictionary (right) compared to a base directory (left).
*
* @author Louis Charette
*/
class Compare
{
/**
* Compares keys and values from left dictionary against right dictionary and returns the difference.
*
* @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: true]
*
* @return string[]
*/
public static function dictionaries(DictionaryInterface $leftDictionary, DictionaryInterface $rightDictionary, bool $undot = false): array
{
$diff = array_diff_assoc($leftDictionary->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_assoc($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;
}
}
181 changes: 181 additions & 0 deletions src/Dictionary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<?php

/*
* UserFrosting i18n (http://www.userfrosting.com)
*
* @link https://github.com/userfrosting/i18n
* @copyright Copyright (c) 2013-2019 Alexander Weissman, Louis Charette
* @license https://github.com/userfrosting/i18n/blob/master/LICENSE.md (MIT License)
*/

namespace UserFrosting\I18n;

use Illuminate\Support\Arr;
use UserFrosting\Support\Repository\Loader\ArrayFileLoader;
use UserFrosting\Support\Repository\Loader\FileRepositoryLoader;
use UserFrosting\Support\Repository\Repository;
use UserFrosting\UniformResourceLocator\ResourceLocatorInterface;

/**
* Locale Dictionary.
*
* Load all locale all "Key => translation" data matrix
*
* @author Louis Charette
*/
class Dictionary extends Repository implements DictionaryInterface
{
/**
* @var string Base URI for locator
*/
protected $uri = 'locale://';

/**
* @var LocaleInterface
*/
protected $locale;

/**
* @var ResourceLocatorInterface
*/
protected $locator;

/**
* @var FileRepositoryLoader
*/
protected $fileLoader;

/**
* @var (string|array)[] Locale "Key => translation" data matrix cache
*/
protected $items = [];

/**
* @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, FileRepositoryLoader $fileLoader = null)
{
$this->locale = $locale;
$this->locator = $locator;
$this->fileLoader = is_null($fileLoader) ? new ArrayFileLoader([]) : $fileLoader;
}

/**
* {@inheritdoc}
*/
public function getDictionary(): array
{
if (empty($this->items)) {
$this->items = $this->loadDictionary();
}

return $this->items;
}

/**
* {@inheritdoc}
*/
public function getFlattenDictionary(): array
{
return Arr::dot($this->getDictionary());
}

/**
* Set the locator base URI (default 'locale://').
*
* @param string $uri
*/
public function setUri(string $uri): void
{
$this->uri = $uri;
}

/**
* {@inheritdoc}
*/
public function getLocale(): LocaleInterface
{
return $this->locale;
}

/**
* Return the file repository loader used to load.
*
* @return FileRepositoryLoader
*/
public function getFileLoader(): FileRepositoryLoader
{
return $this->fileLoader;
}

/**
* Load the dictionary from file.
*
* @return (string|array)[] The locale dictionary
*/
protected function loadDictionary(): array
{
$dictionary = [];

// List of loaded locales
$loadedLocale = [$this->locale->getIdentifier()];

// Get list of files to load
$files = $this->getFiles();
$files = $this->filterDictionaryFiles($files);

// Load all files content if files are present
if (!empty($files)) {
$loader = $this->getFileLoader();
$loader->setPaths($files);

$dictionary = $loader->load();
}

// Now load dependent dictionnaries
foreach ($this->locale->getDependentLocales() as $locale) {

// Stop if locale already loaded to prevent recursion
$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));
}

$dependentDictionary = new self($locale, $this->locator, $this->fileLoader);
$dictionary = array_replace_recursive($dependentDictionary->getDictionary(), $dictionary);

$loadedLocale[] = $locale->getIdentifier();
}

return $dictionary;
}

/**
* Remove config files from locator results and convert ResourceInterface to path/string.
*
* @param \UserFrosting\UniformResourceLocator\ResourceInterface[] $files
*
* @return string[]
*/
protected function filterDictionaryFiles(array $files): array
{
return array_filter($files, function ($file) {
if ($file->getExtension() == 'php') {
return (string) $file;
}
});
}

/**
* List all files for a given locale using the locator.
*
* @return \UserFrosting\UniformResourceLocator\ResourceInterface[]
*/
protected function getFiles(): array
{
return $this->locator->listResources($this->uri.$this->locale->getIdentifier(), true);
}
}
Loading

0 comments on commit 9f2e255

Please sign in to comment.