Files
usbcheck.it/tools/i18n_collect_keys.php
2025-11-27 23:30:44 +01:00

372 lines
9.7 KiB
PHP

<?php
/*
* tools/i18n_collect_keys.php
*
* Sammelt alle verwendeten Übersetzungs-Keys aus:
* - /public/landingpage/{*}/{*}
* - /partials/landing/{*}/{*}
* - /partials/structure/{*}/{*}
* - (optional) /partials/partials/{*}/{*}, falls vorhanden
*
* und trägt fehlende Keys in public/assets/i18n/de.json ein.
*
* Aufruf:
* - CLI: php tools/i18n_collect_keys.php
* - HTTP: https://.../tools/i18n_collect_keys.php
*/
declare(strict_types=1);
$baseDir = dirname(__DIR__);
$deJson = $baseDir . '/public/assets/i18n/de.json';
// -------------------------------
// 1) Bestehende de.json laden
// -------------------------------
if (!is_file($deJson)) {
die("de.json nicht gefunden unter: $deJson\n");
}
$raw = file_get_contents($deJson);
if ($raw === false) {
die("Konnte de.json nicht lesen: $deJson\n");
}
$data = json_decode($raw, true);
if (!is_array($data)) {
die("Ungültiges JSON in de.json\n");
}
if (!isset($data['meta']) || !is_array($data['meta'])) {
$data['meta'] = [
'code' => 'de',
'label' => 'Deutsch',
'flag' => '🇩🇪',
];
}
// -------------------------------
// 2) Verzeichnisse zum Scannen
// -------------------------------
$scanDirs = [
$baseDir . '/public/landingpage',
$baseDir . '/partials/landing',
$baseDir . '/partials/structure',
$baseDir . '/partials/partials', // nur, falls vorhanden
];
$allowedExtensions = ['php', 'html', 'htm'];
// -------------------------------
// 3) Helper-Funktionen
// -------------------------------
function dotKeyExists(array $data, string $key): bool
{
$segments = explode('.', $key);
$node = $data;
foreach ($segments as $seg) {
if (!is_array($node) || !array_key_exists($seg, $node)) {
return false;
}
$node = $node[$seg];
}
return true;
}
function addDotKey(array &$data, string $key, string $default = ''): void
{
if (dotKeyExists($data, $key)) {
return;
}
$segments = explode('.', $key);
$node =& $data;
$last = array_pop($segments);
foreach ($segments as $seg) {
if (!isset($node[$seg]) || !is_array($node[$seg])) {
$node[$seg] = [];
}
$node =& $node[$seg];
}
if (!array_key_exists($last, $node)) {
$node[$last] = $default;
}
}
function simpleKeyExistsRecursive(array $data, string $key): bool
{
foreach ($data as $k => $v) {
if ($k === $key) {
return true;
}
if (is_array($v) && simpleKeyExistsRecursive($v, $key)) {
return true;
}
}
return false;
}
function addSimpleKey(array &$data, string $key, string $defaultValue = null): void
{
if (simpleKeyExistsRecursive($data, $key)) {
return;
}
if (!isset($data['auto']) || !is_array($data['auto'])) {
$data['auto'] = [];
}
if ($defaultValue === null || $defaultValue === '') {
$defaultValue = $key;
}
if (!array_key_exists($key, $data['auto'])) {
$data['auto'][$key] = $defaultValue;
}
}
/**
* Key einfügen, mit optionalem Default-Text.
* Dot-Notation → verschachtelt,
* Simple-Key → unter "auto".
*/
function addKeyToData(array &$data, string $key, ?string $defaultValue = null): void
{
$key = trim($key);
if ($key === '') {
return;
}
$default = ($defaultValue !== null && $defaultValue !== '')
? $defaultValue
: $key;
if (strpos($key, '.') !== false) {
addDotKey($data, $key, $default);
} else {
addSimpleKey($data, $key, $default);
}
}
/**
* Extrahiert den „aktuellen Inhalt“ eines data-i18n-Elements
* ganz grob über Regex:
* ... data-i18n="key"> Inhalt </...
* Ist nur ein Best-Effort; bei komplexeren Strukturen fällt es auf null zurück.
*/
function extractInlineTextForDataI18n(string $content, int $matchOffset, int $matchLength): ?string
{
$start = $matchOffset + $matchLength;
$gtPos = strpos($content, '>', $start);
if ($gtPos === false) {
return null;
}
$closePos = strpos($content, '<', $gtPos + 1);
if ($closePos === false) {
return null;
}
$inner = substr($content, $gtPos + 1, $closePos - $gtPos - 1);
$inner = trim($inner);
if ($inner === '') {
return null;
}
// Ganz grob Tags entfernen
$inner = strip_tags($inner);
$inner = trim($inner);
return $inner !== '' ? $inner : null;
}
/**
* Scannt eine Datei nach i18n-Keys und möglichen Default-Texten.
*
* Rückgabe:
* [ 'key1' => 'Default-Text oder null', 'key2' => null, ... ]
*/
function collectKeysFromFile(string $file): array
{
$content = file_get_contents($file);
if ($content === false || $content === '') {
return [];
}
$keysWithDefaults = [];
// --- data-i18n="key" / 'key' ---
if (preg_match_all('/data-i18n\s*=\s*(["\'])(.+?)\1/i', $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
foreach ($matches as $m) {
$fullMatch = $m[0][0];
$fullOffset = $m[0][1];
$key = trim($m[2][0]);
// Versuche, den Inline-Text zu extrahieren: >Text<
$inlineText = extractInlineTextForDataI18n($content, $fullOffset, strlen($fullMatch));
if (!array_key_exists($key, $keysWithDefaults)) {
$keysWithDefaults[$key] = $inlineText;
} elseif ($keysWithDefaults[$key] === null && $inlineText !== null) {
// Falls wir bisher keinen Default hatten, aber jetzt einen finden:
$keysWithDefaults[$key] = $inlineText;
}
}
}
// --- i18n_get('path.to.key', 'Default') ---
if (preg_match_all(
'/\bi18n_get\s*\(\s*(["\'])([^"\']+)\1\s*(,\s*(["\'])(.*?)\4)?/i',
$content,
$m2,
PREG_SET_ORDER
)) {
foreach ($m2 as $match) {
$key = trim($match[2]);
$default = isset($match[5]) ? trim($match[5]) : null;
if ($default !== null && $default !== '') {
// expliziter Default im PHP-Code → höchste Priorität
$keysWithDefaults[$key] = $default;
} else {
if (!array_key_exists($key, $keysWithDefaults)) {
$keysWithDefaults[$key] = null;
}
}
}
}
// --- i18n_get_fmt("path.to.key", "Default", ...) ---
if (preg_match_all(
'/\bi18n_get_fmt\s*\(\s*(["\'])([^"\']+)\1\s*(,\s*(["\'])(.*?)\4)?/i',
$content,
$m3,
PREG_SET_ORDER
)) {
foreach ($m3 as $match) {
$key = trim($match[2]);
$default = isset($match[5]) ? trim($match[5]) : null;
if ($default !== null && $default !== '') {
$keysWithDefaults[$key] = $default;
} else {
if (!array_key_exists($key, $keysWithDefaults)) {
$keysWithDefaults[$key] = null;
}
}
}
}
return $keysWithDefaults;
}
// -------------------------------
// 4) Dateien durchlaufen & Keys sammeln
// -------------------------------
$foundKeys = []; // key => defaultText|null
foreach ($scanDirs as $dir) {
if (!is_dir($dir)) {
continue;
}
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($it as $fileInfo) {
/** @var SplFileInfo $fileInfo */
if (!$fileInfo->isFile()) {
continue;
}
$ext = strtolower($fileInfo->getExtension());
if (!in_array($ext, $allowedExtensions, true)) {
continue;
}
$filePath = $fileInfo->getPathname();
$keysInFile = collectKeysFromFile($filePath);
foreach ($keysInFile as $key => $defaultText) {
if (!array_key_exists($key, $foundKeys)) {
$foundKeys[$key] = $defaultText;
} else {
// Wenn wir bisher keinen Default hatten, aber jetzt einen haben → übernehmen
if (($foundKeys[$key] === null || $foundKeys[$key] === '')
&& $defaultText !== null
&& $defaultText !== ''
) {
$foundKeys[$key] = $defaultText;
}
}
}
}
}
// -------------------------------
// 5) Gefundene Keys in de.json eintragen
// -------------------------------
$addedCount = 0;
$skippedCount = 0;
$newKeys = [];
foreach ($foundKeys as $key => $defaultText) {
$before = json_encode($data);
addKeyToData($data, $key, $defaultText);
$after = json_encode($data);
if ($before === $after) {
$skippedCount++;
} else {
$addedCount++;
$newKeys[] = $key;
}
}
// -------------------------------
// 6) de.json zurückschreiben
// -------------------------------
$newJson = json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
if ($newJson === false) {
die("Fehler beim JSON-Encode von de.json\n");
}
if (file_put_contents($deJson, $newJson) === false) {
die("Konnte de.json nicht schreiben: $deJson\n");
}
// -------------------------------
// 7) Ausgabe
// -------------------------------
$isCli = (php_sapi_name() === 'cli');
$output = "i18n-Collect abgeschlossen.\n";
$output .= "Gefundene Keys gesamt: " . count($foundKeys) . "\n";
$output .= "Neu hinzugefügt: " . $addedCount . "\n";
$output .= "Übersprungen (bereits vorhanden): $skippedCount\n";
$output .= "Datei aktualisiert: $deJson\n";
if (!empty($newKeys)) {
$output .= "\nNeu angelegte Keys:\n";
foreach ($newKeys as $k) {
$output .= " - " . $k . "\n";
}
}
if ($isCli) {
echo $output;
} else {
header('Content-Type: text/plain; charset=utf-8');
echo $output;
}