i18n collect
This commit is contained in:
@@ -5,7 +5,7 @@ stages:
|
|||||||
# Globale Variablen
|
# Globale Variablen
|
||||||
# -----------------------------------------
|
# -----------------------------------------
|
||||||
variables:
|
variables:
|
||||||
BASE_DIRS: "src public api partials"
|
BASE_DIRS: "src public api partials tools"
|
||||||
CONFIG_BASE_DIR: "config"
|
CONFIG_BASE_DIR: "config"
|
||||||
|
|
||||||
# Standard-Image für alle Jobs
|
# Standard-Image für alle Jobs
|
||||||
|
|||||||
8
public/landingpage/tools/i18n_collect.php
Normal file
8
public/landingpage/tools/i18n_collect.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
// public/landingpages/tools/i18n_collect.php
|
||||||
|
|
||||||
|
// Projektbasis ermitteln: /public/landingpages/tools -> / (Projektroot)
|
||||||
|
$baseDir = dirname(__DIR__, 2);
|
||||||
|
|
||||||
|
// Tool-Script einbinden und ausführen
|
||||||
|
require_once $baseDir . '/tools/i18n_collect_keys.php';
|
||||||
371
tools/i18n_collect_keys.php
Normal file
371
tools/i18n_collect_keys.php
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user