Files
hotel/docker/find-missing-admin-translations.php
DanielS 661cf645d4 German back office: hotel modules, reports, dashboard and menu names
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>
2026-11-12 00:41:51 +01:00

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);