Skip to content

Commit

Permalink
Add more tests and alias-to-alias support.
Browse files Browse the repository at this point in the history
  • Loading branch information
demiankatz committed Dec 20, 2023
1 parent 0631727 commit 4979b9b
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 33 deletions.
95 changes: 64 additions & 31 deletions module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIni.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,48 @@ protected function loadLanguageLocale($locale, $domain, $processAliases = false)
// and we're not dealing with text domains. A missing base file is an
// unexpected, fatal error; a missing domain-specific file is more likely
// due to the possibility of incomplete translations.
return $this->loadLanguageFile($filename, empty($domain), $processAliases);
return $this->loadLanguageFile($filename, empty($domain), $processAliases ? $domain ?? 'default' : null);
}

/**
* Resolve a single alias (or return null if it cannot be resolved)
*
* @param array $alias The [domain, key] or [key] alias array
* @param string $defaultDomain The domain to use if $alias does not specify one
* @param string $locale The locale currently being loaded
* @param array $breadcrumbs Previously-resolved aliases (to prevent infinite loops)
*
* @return ?string
*/
protected function resolveAlias(
array $alias,
string $defaultDomain,
string $locale,
array $breadcrumbs = []
): ?string {
// If the current alias target does not include a TextDomain part, assume it refers
// to the current active TextDomain:
if (count($alias) < 2) {
array_unshift($alias, $defaultDomain);
}
[$domain, $key] = $alias;

// If the alias references another TextDomain, we need to load that now.
if (!isset($this->aliasDomains[$domain])) {
$this->aliasDomains[$domain] = $this->loadLanguageLocale($locale, $domain, true);
}
if ($this->aliasDomains[$domain]->offsetExists($key)) {
return $this->aliasDomains[$domain]->offsetGet($key);
} elseif (isset($this->aliases[$domain][$key])) {
// Circular alias infinite loop prevention:
$breadcrumbKey = "$domain::$key";
if (in_array($breadcrumbKey, $breadcrumbs)) {
return null;
}
$breadcrumbs[] = $breadcrumbKey;
return $this->resolveAlias($this->aliases[$domain][$key], $domain, $locale, $breadcrumbs);
}
return null;
}

/**
Expand All @@ -254,25 +295,14 @@ protected function loadLanguageLocale($locale, $domain, $processAliases = false)
*/
protected function applyAliases(TextDomain $data, string $currentLocale, string $currentDomain): void
{
foreach ($this->aliases as $alias => $target) {
// If the current alias target does not include a TextDomain part, assume it refers
// to the current active TextDomain:
if (count($target) < 2) {
array_unshift($target, $currentDomain);
}
[$domain, $key] = $target;
// If the alias references another TextDomain, we need to load that now; note that we
// do not process aliases again at this step, so aliases to aliases will not work.
if (!isset($this->aliasDomains[$domain])) {
$this->aliasDomains[$domain] = $this->loadLanguageLocale($currentLocale, $domain);
}
foreach ($this->aliases[$currentDomain] ?? [] as $alias => $target) {
// Do not overwrite existing values with alias, and do not create aliases
// when target values are missing.
if (
$this->aliasDomains[$domain]->offsetExists($key)
&& !$data->offsetExists($alias)
!$data->offsetExists($alias)
&& $aliasValue = $this->resolveAlias($target, $currentDomain, $currentLocale)
) {
$data->offsetSet($alias, $this->aliasDomains[$domain]->offsetGet($key));
$data->offsetSet($alias, $aliasValue);
}
}
}
Expand Down Expand Up @@ -326,29 +356,31 @@ protected function normalizeAlias(string $alias): array
*
* @return void
*/
protected function markAndLoadAliases(string $filename): void
protected function markAndLoadAliases(string $aliasDomain, string $filename): void
{
if (!in_array($filename, $this->loadedAliasFiles)) {
$this->loadedAliasFiles[] = $filename;
$loadedFiles = $this->loadedAliasFiles[$aliasDomain] ?? [];
if (!in_array($filename, $loadedFiles)) {
$this->loadedAliasFiles[$aliasDomain] = array_merge($loadedFiles, [$filename]);
if (file_exists($filename)) {
// Parse and normalize the alias configuration:
$newAliases = array_map([$this, 'normalizeAlias'], parse_ini_file($filename));
// Merge with pre-existing aliases:
$this->aliases = array_merge($this->aliases, $newAliases);
$this->aliases[$aliasDomain] = array_merge($this->aliases[$aliasDomain] ?? [], $newAliases);
}
}
}

/**
* Search the path stack for language files and merge them together.
*
* @param string $filename Name of file to search path stack for.
* @param bool $failOnError If true, throw an exception when file not found.
* @param bool $processAliases Should we process alias data?
* @param string $filename Name of file to search path stack for.
* @param bool $failOnError If true, throw an exception when file not found.
* @param bool $aliasDomain Name of TextDomain for which we should process aliases
* (or null to skip alias processing)
*
* @return TextDomain
*/
protected function loadLanguageFile($filename, $failOnError = true, $processAliases = false)
protected function loadLanguageFile($filename, $failOnError, ?string $aliasDomain)
{
// Don't load a file that has already been loaded:
if ($this->checkAndMarkLoadedFile($filename)) {
Expand All @@ -362,16 +394,16 @@ protected function loadLanguageFile($filename, $failOnError = true, $processAlia
// Load current file with parent data, if necessary:
$current = $this->loadParentData(
$this->reader->getTextDomain($fileOnPath),
$processAliases
$aliasDomain
);
if ($data === false) {
$data = $current;
} else {
$data->merge($current);
}
}
if ($processAliases) {
$this->markAndLoadAliases(dirname($fileOnPath) . '/aliases.ini');
if ($aliasDomain) {
$this->markAndLoadAliases($aliasDomain, dirname($fileOnPath) . '/aliases.ini');
}
}
if ($data === false) {
Expand All @@ -390,17 +422,18 @@ protected function loadLanguageFile($filename, $failOnError = true, $processAlia
/**
* Support method for loadLanguageFile: retrieve parent data.
*
* @param TextDomain $data TextDomain to populate with parent information.
* @param bool $processAliases Should we process alias data?
* @param TextDomain $data TextDomain to populate with parent information.
* @param bool $aliasDomain Name of TextDomain for which we should process aliases
* (or null to skip alias processing)
*
* @return TextDomain
*/
protected function loadParentData($data, $processAliases)
protected function loadParentData($data, ?string $aliasDomain)
{
if (!isset($data['@parent_ini'])) {
return $data;
}
$parent = $this->loadLanguageFile($data['@parent_ini'], true, $processAliases);
$parent = $this->loadLanguageFile($data['@parent_ini'], true, $aliasDomain);
$parent->merge($data);
return $parent;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foofoo = default::foo
3 changes: 2 additions & 1 deletion module/VuFind/tests/fixtures/language/aliases/aliases.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
foo = bar
baz = Domain::bar
xyzzy = Domain::bar
xyzzy = Domain::bar
foofoo = Domain::foofoo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
baz = default::baz
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
baz = Domain::baz
bar = xyzzy
xyzzy = bar
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = bar
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,27 @@ public function testAliasing(): void
'baz' => 'Domain Translation',
'foo' => 'Translation',
'xyzzy' => 'Domain Translation',
'foofoo' => 'Translation',
],
(array)$result
);
}

/**
* Test circular alias infinite loop prevention.
*
* @return void
*/
public function testCircularAliasSafety(): void
{
$pathStack = [
realpath($this->getFixtureDir() . 'language/circularaliases'),
];
$loader = new ExtendedIni($pathStack, 'en');
$result = $loader->load('en', null);
$this->assertEquals(
[
'foo' => 'bar',
],
(array)$result
);
Expand All @@ -212,7 +233,8 @@ public function testInheritedAliasing(): void
'baz' => 'Domain Translation',
'foo' => 'Translation',
'xyzzy' => 'Child Overriding Alias',
'@parent_ini' => 'en.ini'
'@parent_ini' => 'en.ini',
'foofoo' => 'Translation',
],
(array)$result
);
Expand Down

0 comments on commit 4979b9b

Please sign in to comment.