The German language pack leaves the Webkul hotel modules (Hotel Reservation System, Book Now, refunds, rooms, settings), reports, dashboard widgets, cron manager, channel manager, reviews and 31 menu names in English. - docker/find-missing-admin-translations.php: scans module admin controllers/classes/templates and core admin pages for keys without a German text - docker/translations-de-admin.php: 1361 translations (formal "Sie", shared glossary, placeholders/markup verified automatically) - docker/translations-de-tabs.php: menu names (Hotelverwaltung, Zimmer buchen, Zimmerstatus, Erstattungsanträge, Zeitgesteuerte Aufgaben ...) - apply-translations.php now also writes back-office module texts, core admin texts and menu names; module texts the core admin pack already translates are reused from it (modules do not fall back to the admin pack at runtime). Result locally: 0 untranslated strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
6.6 KiB
PHP
168 lines
6.6 KiB
PHP
<?php
|
|
/**
|
|
* Fills translation gaps the official language pack leaves (shop, back office, menu names).
|
|
* docker compose exec -u www-data app php docker/apply-translations.php [iso]
|
|
*
|
|
* Dictionaries (English => target language), only missing keys are written, existing
|
|
* translations are never overwritten:
|
|
* docker/translations-<iso>-front.php shop texts (scanner: find-missing-translations.php)
|
|
* docker/translations-<iso>-admin.php back-office texts (scanner: find-missing-admin-translations.php)
|
|
* docker/translations-<iso>-tabs.php menu names (tab class => name), English names only
|
|
* Module texts go to modules/<module>/translations/<iso>.php, core admin texts to
|
|
* translations/<iso>/admin.php (both git-ignored upstream: re-run after every install).
|
|
*/
|
|
if (PHP_SAPI !== 'cli') {
|
|
exit;
|
|
}
|
|
$iso = isset($argv[1]) ? $argv[1] : 'de';
|
|
$root = realpath(__DIR__.'/..');
|
|
|
|
function loadDict($file)
|
|
{
|
|
return is_file($file) ? require $file : array();
|
|
}
|
|
|
|
function scan($script, $iso)
|
|
{
|
|
$json = shell_exec('php '.escapeshellarg(__DIR__.'/'.$script).' '.escapeshellarg($iso).' 2>/dev/null');
|
|
$data = json_decode((string)$json, true);
|
|
if (!is_array($data)) {
|
|
fwrite(STDERR, "Could not run $script\n");
|
|
exit(1);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/** Append $_MODULE lines before a trailing "return $_MODULE;" if the pack file has one. */
|
|
function appendModuleLines($root, $mod, $iso, array $lines)
|
|
{
|
|
$file = $root.'/modules/'.$mod.'/translations/'.$iso.'.php';
|
|
if (!is_file($file)) {
|
|
@mkdir(dirname($file), 0755, true);
|
|
file_put_contents($file, "<?php\n\nglobal \$_MODULE;\n\$_MODULE = array();\n");
|
|
}
|
|
$content = rtrim(file_get_contents($file));
|
|
if (substr($content, -2) === '?>') {
|
|
$content = rtrim(substr($content, 0, -2));
|
|
}
|
|
$hasReturn = (bool)preg_match('/\n\s*return \$_MODULE;\s*$/', $content);
|
|
if ($hasReturn) {
|
|
$content = rtrim(preg_replace('/\n\s*return \$_MODULE;\s*$/', '', $content));
|
|
}
|
|
$content .= "\n\n// Added by docker/apply-translations.php\n".implode("\n", $lines)."\n";
|
|
if ($hasReturn) {
|
|
$content .= "\nreturn \$_MODULE;\n";
|
|
}
|
|
file_put_contents($file, $content);
|
|
}
|
|
|
|
function applyModules($root, $iso, array $missing, array $dict, &$untranslated)
|
|
{
|
|
$added = 0;
|
|
foreach ($missing as $mod => $entries) {
|
|
$lines = array();
|
|
foreach ($entries as $key => $english) {
|
|
if (!isset($dict[$english]) || !is_string($dict[$english])) {
|
|
$untranslated[] = $english;
|
|
continue;
|
|
}
|
|
$lines[] = '$_MODULE['.var_export($key, true).'] = '.var_export($dict[$english], true).';';
|
|
}
|
|
if ($lines) {
|
|
appendModuleLines($root, $mod, $iso, $lines);
|
|
$added += count($lines);
|
|
echo sprintf(" %-26s +%d\n", $mod, count($lines));
|
|
}
|
|
}
|
|
return $added;
|
|
}
|
|
|
|
$untranslated = array();
|
|
|
|
echo "Shop and module templates\n";
|
|
$front = loadDict(__DIR__.'/translations-'.$iso.'-front.php');
|
|
$admin = loadDict(__DIR__.'/translations-'.$iso.'-admin.php');
|
|
$missing = scan('find-missing-translations.php', $iso);
|
|
// the template scanner also finds dashboard/report modules: their texts live in the admin dictionary
|
|
$added = applyModules($root, $iso, $missing['modules'], $front + $admin, $untranslated);
|
|
|
|
// Theme strings the language pack "translates" with English text: replace those values.
|
|
$themeFixes = isset($front['__theme_value_fixes']) ? $front['__theme_value_fixes'] : array();
|
|
$themeFile = $root.'/themes/hotel-reservation-theme/lang/'.$iso.'.php';
|
|
if ($themeFixes && is_file($themeFile)) {
|
|
$c = file_get_contents($themeFile);
|
|
$n = 0;
|
|
foreach ($themeFixes as $from => $to) {
|
|
$c = str_replace('= '.var_export($from, true).';', '= '.var_export($to, true).';', $c, $count);
|
|
$n += $count;
|
|
}
|
|
file_put_contents($themeFile, $c);
|
|
echo " theme lang fixes $n\n";
|
|
}
|
|
|
|
if ($admin) {
|
|
echo "Back office\n";
|
|
$missingAdmin = scan('find-missing-admin-translations.php', $iso);
|
|
// texts the core admin pack already translates (same English text): reuse its German value
|
|
$_LANGADM = array();
|
|
if (is_file($root.'/translations/'.$iso.'/admin.php')) {
|
|
include $root.'/translations/'.$iso.'/admin.php';
|
|
}
|
|
$byMd5 = array();
|
|
foreach ($_LANGADM as $k => $v) {
|
|
if ($v !== '') {
|
|
$byMd5[substr($k, -32)] = $v;
|
|
}
|
|
}
|
|
$packDict = array();
|
|
foreach ($missingAdmin['modules'] as $entries) {
|
|
foreach ($entries as $english) {
|
|
if (!isset($admin[$english]) && isset($byMd5[md5($english)])) {
|
|
$packDict[$english] = $byMd5[md5($english)];
|
|
}
|
|
}
|
|
}
|
|
$added += applyModules($root, $iso, $missingAdmin['modules'], $admin + $packDict, $untranslated);
|
|
$lines = array();
|
|
foreach ($missingAdmin['core'] as $key => $english) {
|
|
if (isset($admin[$english])) {
|
|
$lines[] = '$_LANGADM['.var_export($key, true).'] = '.var_export($admin[$english], true).';';
|
|
} else {
|
|
$untranslated[] = $english;
|
|
}
|
|
}
|
|
if ($lines) {
|
|
$file = $root.'/translations/'.$iso.'/admin.php';
|
|
$content = is_file($file) ? rtrim(file_get_contents($file)) : "<?php\n\nglobal \$_LANGADM;\n\$_LANGADM = array();";
|
|
$content = preg_replace('/\?>\s*$/', '', $content);
|
|
file_put_contents($file, rtrim($content)."\n\n// Added by docker/apply-translations.php\n".implode("\n", $lines)."\n");
|
|
$added += count($lines);
|
|
echo sprintf(" %-26s +%d\n", 'core admin', count($lines));
|
|
}
|
|
}
|
|
|
|
$tabs = loadDict(__DIR__.'/translations-'.$iso.'-tabs.php');
|
|
if ($tabs) {
|
|
echo "Menu\n";
|
|
define('_PS_ADMIN_DIR_', __DIR__);
|
|
require_once $root.'/config/config.inc.php';
|
|
$idLang = (int)Language::getIdByIso($iso);
|
|
$n = 0;
|
|
foreach ($tabs as $class => $name) {
|
|
$idTab = (int)Tab::getIdFromClassName($class);
|
|
if (!$idTab || !$idLang) {
|
|
continue;
|
|
}
|
|
$current = (string)Db::getInstance()->getValue('SELECT `name` FROM `'._DB_PREFIX_.'tab_lang` WHERE `id_tab` = '.$idTab.' AND `id_lang` = '.$idLang);
|
|
// only replace names that are still the English default (leave own renames alone)
|
|
if ($current !== $name && preg_match('/^[\x20-\x7E]*$/', $current) && !preg_match('/(ung|en|e)$/', $current)) {
|
|
Db::getInstance()->update('tab_lang', array('name' => pSQL($name)), '`id_tab` = '.$idTab.' AND `id_lang` = '.$idLang);
|
|
$n++;
|
|
}
|
|
}
|
|
echo " menu names $n\n";
|
|
}
|
|
|
|
echo "Added $added translations.\n";
|
|
echo 'Not in dictionary: '.count(array_unique($untranslated))."\n";
|