226 lines
7.3 KiB
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;
|