b2in/dev/immobilien 07-05-2026/fix-umlauts.php

226 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
/**
* Convert ae/oe/ue/ss in German fields to proper umlauts (ä/ö/ü/ß).
*
* Strategy:
* - Keep English/quote fields untouched (`official_*`, `slug`, `image`, urls).
* - Use a curated word-level mapping so we never change English loanwords
* (Avenue, Asset, Business, Boutique, Resale, ...).
* - Apply replacements to every German string field (recursive walk).
*/
$file = __DIR__.'/../../resources/lang/de/immobilien-azizi.json';
$json = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);
/**
* Keys whose string values must NOT be touched. Includes Azizi-source quotes
* and structural fields.
*/
$skipKeys = [
'official_headline',
'official_description',
'official_unit_type',
'official_starting_price',
'official_url',
'slug',
'image',
'category',
'status_group',
'data_confidence',
'overview_bedrooms',
'key',
];
/**
* Word-level replacements. Order matters: longer words first so we don't
* partially match a shorter sibling.
*
* Each replacement replaces the *entire word* via word-boundary regex.
*/
$wordMap = [
// ae / Ae
'Bauchgefuehl' => 'Bauchgefühl',
'Bestandskaeufer' => 'Bestandskäufer',
'Datensaetze' => 'Datensätze',
'Einschaetzung' => 'Einschätzung',
'Ergaenzt' => 'Ergänzt',
'Ergaenzung' => 'Ergänzung',
'Erstkaeufer' => 'Erstkäufer',
'Erzaehlung' => 'Erzählung',
'Europaeisch' => 'Europäisch',
'europaeisch' => 'europäisch',
'Flaechenbedarf' => 'Flächenbedarf',
'Flaechenzuschnitte' => 'Flächenzuschnitte',
'Gebaeude' => 'Gebäude',
'Gebaeuden' => 'Gebäuden',
'Gebaeudehoehe' => 'Gebäudehöhe',
'Kaeufen' => 'Käufen',
'Kaeufer' => 'Käufer',
'Kaeufern' => 'Käufern',
'Naehe' => 'Nähe',
'Praeferenz' => 'Präferenz',
'Projektqualitaet' => 'Projektqualität',
'Renditekaeufer' => 'Renditekäufer',
'Vergleichsadresse' => 'Vergleichsadresse', // unchanged (no umlaut needed)
'Vermietungskaeufer' => 'Vermietungskäufer',
'bewaehrten' => 'bewährten',
'ergaenzen' => 'ergänzen',
'ergaenzt' => 'ergänzt',
'zusaetzlich' => 'zusätzlich',
'Projektuebersicht' => 'Projektübersicht',
'Waehrungslogik' => 'Währungslogik',
'Zentralitaetskaeufer' => 'Zentralitätskäufer',
'abhaengig' => 'abhängig',
'erklaert' => 'erklärt',
'faellt' => 'fällt',
'klaeren' => 'klären',
'laeuft' => 'läuft',
'naechsten' => 'nächsten',
'spaeter' => 'später',
'spaetere' => 'spätere',
'waehrend' => 'während',
'zaehlen' => 'zählen',
'zaehlt' => 'zählt',
// ae mid-word (compound and case variants)
'Exklusivitaet' => 'Exklusivität',
'Liquiditaet' => 'Liquidität',
'Vermietungsstaerke' => 'Vermietungsstärke',
'Staerke' => 'Stärke',
'Wohnflaeche' => 'Wohnfläche',
'Wohnflaechensegment' => 'Wohnflächensegment',
'groessenstaerkere' => 'größenstärkere',
// ue / Ue
'Frueher' => 'Früher',
'frueh' => 'früh',
'fruehe' => 'frühe',
'Fuenf' => 'Fünf',
'Fuer' => 'Für',
'fuer' => 'für',
'Gruenflaechen' => 'Grünflächen',
'Kuechen' => 'Küchen',
'Kuerzlich' => 'Kürzlich',
'Masterplaene' => 'Masterpläne',
'Moebel' => 'Möbel',
'Persoenlich' => 'Persönlich',
'Persoenliche' => 'Persönliche',
'Suedwestlicher' => 'Südwestlicher',
'Tuerme' => 'Türme',
'Verfuegbar' => 'Verfügbar',
'Verfuegbarkeit' => 'Verfügbarkeit',
'Verfuegbarkeitspruefung' => 'Verfügbarkeitsprüfung',
'Vergleichsmoeglichkeiten' => 'Vergleichsmöglichkeiten',
'Wohnungsgroessen' => 'Wohnungsgrößen',
'Investmentgroessen' => 'Investmentgrößen',
'Zahlungsschritte' => 'Zahlungsschritte', // unchanged
'Zusaetzliche' => 'Zusätzliche',
'dafuer' => 'dafür',
'eroeffnen' => 'eröffnen',
'fuehrt' => 'führt',
'gegenueber' => 'gegenüber',
'gehoert' => 'gehört',
'geprueft' => 'geprüft',
'grossen' => 'großen',
'grosses' => 'großes',
'grosse' => 'große',
'Grosses' => 'Großes',
'Grosse' => 'Große',
'grosszuegigen' => 'großzügigen',
'hoeherem' => 'höherem',
'hoeheres' => 'höheres',
'hoeherwertiger' => 'höherwertiger',
'juenger' => 'jünger',
'juengere' => 'jüngere',
'juengeren' => 'jüngeren',
'juengste' => 'jüngste',
'koennen' => 'können',
'kuenstlichem' => 'künstlichem',
'kuenstlicher' => 'künstlicher',
'muessen' => 'müssen',
'nuetzlich' => 'nützlich',
'oestlichen' => 'östlichen',
'oestlicher' => 'östlicher',
'persoenlich' => 'persönlich',
'persoenliche' => 'persönliche',
'pruefe' => 'prüfe',
'pruefen' => 'prüfen',
'schoenste' => 'schönste',
'stoeckiges' => 'stöckiges',
'suedwestliche' => 'südwestliche',
'suedwestlicher' => 'südwestlicher',
'ueber' => 'über',
'Ueber' => 'Über',
'uebergeben' => 'übergeben',
'ueberschaubar' => 'überschaubar',
'ueberzeugend' => 'überzeugend',
'ueberzeugt' => 'überzeugt',
'ueblicherweise' => 'üblicherweise',
'ungewoehnlichem' => 'ungewöhnlichem',
'urspruengliche' => 'ursprüngliche',
'verfuegbar' => 'verfügbar',
'wasserorientiertes' => 'wasserorientiertes', // unchanged
'zeitgenoessischer' => 'zeitgenössischer',
'zuegig' => 'zügig',
'zugaenglicher' => 'zugänglicher',
'zugaenglichsten' => 'zugänglichsten',
// oe / Oe (mostly already covered above; keep explicit ones too)
'franzoesisch' => 'französisch',
'umweltbewusst' => 'umweltbewusst', // unchanged
'Erdgeschoss' => 'Erdgeschoss', // unchanged - no ß
// ss → ß (only true ß-words; loanwords like Adresse, Prozess stay)
'Groesse' => 'Größe',
'groesste' => 'größte',
'groessten' => 'größten',
'groessere' => 'größere',
'Groesserer' => 'Größerer',
'groesseres' => 'größeres',
'Grossanlage' => 'Großanlage',
'Grossentwicklung' => 'Großentwicklung',
'grundsaetzlich' => 'grundsätzlich',
'Strassenanbindung' => 'Straßenanbindung',
'Einkaufsstrassen' => 'Einkaufsstraßen',
'einfliessen' => 'einfließen',
];
/**
* Build regex once: \b(WORD1|WORD2|...)\b with case-sensitive matching.
*/
$keys = array_keys($wordMap);
usort($keys, fn ($a, $b) => strlen($b) <=> strlen($a)); // longest first
$pattern = '/\b('.implode('|', array_map('preg_quote', $keys)).')\b/u';
$replacements = 0;
$walk = function (&$node, $parentKey = null) use (&$walk, $skipKeys, $wordMap, $pattern, &$replacements) {
if (is_array($node)) {
foreach ($node as $k => &$v) {
if (is_string($k) && in_array($k, $skipKeys, true)) {
continue;
}
$walk($v, $k);
}
unset($v);
} elseif (is_string($node) && $parentKey !== null && ! in_array($parentKey, $skipKeys, true)) {
$new = preg_replace_callback($pattern, function ($m) use ($wordMap, &$replacements) {
$replacements++;
return $wordMap[$m[1]];
}, $node);
if ($new !== null) {
$node = $new;
}
}
};
$walk($json);
file_put_contents(
$file,
json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)."\n"
);
echo 'Wort-Ersetzungen: '.$replacements.PHP_EOL;
echo 'Datei gespeichert: '.$file.PHP_EOL;