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>
135 lines
5.1 KiB
PHP
135 lines
5.1 KiB
PHP
<?php
|
|
/**
|
|
* Lists back-office strings without a German translation (iso arg, default de).
|
|
* docker compose ... exec -u www-data app php docker/find-missing-admin-translations.php de > missing.json
|
|
* Covers the hotel modules' admin controllers/classes/templates and the core admin pages
|
|
* the reception works with (orders, customers, carts, guest registration).
|
|
* Output: {"modules": {mod: {key: english}}, "core": {key: english}}
|
|
*/
|
|
if (PHP_SAPI !== 'cli') {
|
|
exit;
|
|
}
|
|
$iso = isset($argv[1]) ? $argv[1] : 'de';
|
|
$root = realpath(__DIR__.'/..');
|
|
|
|
$modules = array('hotelreservationsystem', 'qlohotelreview', 'qlocmconnector', 'wkroomsearchblock', 'blocknavigationmenu', 'qlocrontaskmanager');
|
|
$coreControllers = array('AdminOrders', 'AdminCustomers', 'AdminCarts', 'AdminGuestRegistration', 'AdminAddresses', 'AdminDashboard', 'AdminLogin');
|
|
$coreTplDirs = array('orders', 'customers', 'carts', 'guest_registration', 'addresses', 'dashboard', 'login');
|
|
|
|
$rePhp = '/->l\(\s*\'((?:[^\'\\\\]|\\\\.)*)\'(?:\s*,\s*\'([^\']*)\')?/';
|
|
$reTpl = '/\{l\s+s=(?:\'((?:[^\'\\\\]|\\\\.)*)\'|"((?:[^"\\\\]|\\\\.)*)")(?:[^}]*?\smod=[\'"]([\w-]+)[\'"])?[^}]*\}/';
|
|
|
|
function files($dir, $ext)
|
|
{
|
|
$out = array();
|
|
if (!is_dir($dir)) {
|
|
return $out;
|
|
}
|
|
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));
|
|
foreach ($it as $f) {
|
|
if (substr($f, -strlen($ext)) === $ext) {
|
|
$out[] = (string)$f;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
$out = array('modules' => array(), 'core' => array());
|
|
|
|
// core admin pack: module strings fall back to any admin translation with the same md5
|
|
$_LANGADM = array();
|
|
if (is_file("$root/translations/$iso/admin.php")) {
|
|
include "$root/translations/$iso/admin.php";
|
|
}
|
|
$haveMd5 = array();
|
|
foreach ($_LANGADM as $k => $v) {
|
|
if ($v !== '') {
|
|
$haveMd5[substr($k, -32)] = true;
|
|
}
|
|
}
|
|
|
|
// ---- modules
|
|
foreach ($modules as $mod) {
|
|
$_MODULE = array();
|
|
foreach (array("$root/modules/$mod/translations/$iso.php", "$root/themes/hotel-reservation-theme/modules/$mod/translations/$iso.php") as $f) {
|
|
if (is_file($f)) {
|
|
include $f;
|
|
}
|
|
}
|
|
$have = $_MODULE;
|
|
$add = function ($source, $string) use (&$out, $have, $mod, $haveMd5) {
|
|
$key = strtolower('<{'.$mod.'}prestashop>'.$source).'_'.md5($string);
|
|
// module strings do NOT fall back to the admin pack at runtime: report them all
|
|
if (empty($have[$key])) {
|
|
$out['modules'][$mod][$key] = $string;
|
|
}
|
|
};
|
|
foreach (files("$root/modules/$mod", '.php') as $f) {
|
|
if (strpos($f, '/translations/') !== false || strpos($f, '/vendor/') !== false) {
|
|
continue;
|
|
}
|
|
if (!preg_match_all($rePhp, file_get_contents($f), $m, PREG_SET_ORDER)) {
|
|
continue;
|
|
}
|
|
$base = basename($f, '.php');
|
|
foreach ($m as $x) {
|
|
$string = stripslashes($x[1]);
|
|
if (!empty($x[2])) {
|
|
$source = $x[2];
|
|
} elseif (preg_match('/Controller$/', $base) && strpos($f, '/controllers/admin/') !== false) {
|
|
$source = $base; // ModuleAdminController -> "<class>controller"
|
|
} elseif (strpos($f, '/controllers/') !== false) {
|
|
$source = preg_replace('/\.php$/', '', basename($f)); // front controller file name
|
|
} else {
|
|
$source = $mod;
|
|
}
|
|
$add($source, $string);
|
|
}
|
|
}
|
|
foreach (files("$root/modules/$mod/views/templates/admin", '.tpl') as $f) {
|
|
if (!preg_match_all($reTpl, file_get_contents($f), $m, PREG_SET_ORDER)) {
|
|
continue;
|
|
}
|
|
foreach ($m as $x) {
|
|
$string = stripslashes($x[1] !== '' ? $x[1] : $x[2]);
|
|
$add(basename($f, '.tpl'), $string);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- core admin
|
|
$addCore = function ($class, $string) use (&$out, $_LANGADM, $haveMd5) {
|
|
$md5 = md5($string);
|
|
if (empty($_LANGADM[$class.$md5]) && empty($haveMd5[$md5])) {
|
|
$out['core'][$class.$md5] = $string;
|
|
}
|
|
};
|
|
foreach ($coreControllers as $c) {
|
|
$f = "$root/controllers/admin/{$c}Controller.php";
|
|
if (is_file($f) && preg_match_all($rePhp, file_get_contents($f), $m, PREG_SET_ORDER)) {
|
|
foreach ($m as $x) {
|
|
$addCore($c, stripslashes($x[1]));
|
|
}
|
|
}
|
|
}
|
|
foreach ($coreTplDirs as $i => $dir) {
|
|
$class = isset($coreControllers[$i]) ? $coreControllers[$i] : 'AdminTab';
|
|
foreach (files("$root/admin-dev/themes/default/template/controllers/$dir", '.tpl') as $f) {
|
|
if (preg_match_all($reTpl, file_get_contents($f), $m, PREG_SET_ORDER)) {
|
|
foreach ($m as $x) {
|
|
if (!empty($x[3])) {
|
|
continue; // module string inside a core template
|
|
}
|
|
$addCore($class, stripslashes($x[1] !== '' ? $x[1] : $x[2]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$counts = array('core' => count($out['core']));
|
|
foreach ($out['modules'] as $mod => $s) {
|
|
$counts[$mod] = count($s);
|
|
}
|
|
fwrite(STDERR, json_encode($counts).PHP_EOL);
|
|
echo json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|