Gerichtnamen, Essenszeiten und Allergen-/Zusatzstoff-Bezeichnungen sind jetzt genauso dynamisch mehrsprachig wie die Bedienoberfläche aus v1.10.0, statt fest auf Englisch/Polnisch begrenzt zu sein. Neue Übersetzungstabellen ersetzen die alten name_en/name_pl-Spalten, DeepL-Auto-Übersetzung und CSV-Import/-Export unterstützen jetzt beliebig viele aktive Sprachen.
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import "server-only";
|
||
import { query } from "@/lib/db";
|
||
|
||
// Gemeinsame Zugriffsschicht für die vier Übersetzungstabellen aus
|
||
// db/migrations/0006_content_translations.sql. Die Wert-Spalte heißt
|
||
// in allen vieren bewusst einheitlich "name" (auch bei
|
||
// section_translations, obwohl sections.label selbst anders heißt) –
|
||
// vereinfacht diese generischen Helfer, statt zusätzlich eine Spalten-
|
||
// Namens-Zuordnung pflegen zu müssen.
|
||
export type TranslationTable =
|
||
| "dish_translations"
|
||
| "section_translations"
|
||
| "allergen_translations"
|
||
| "additive_translations";
|
||
|
||
const ID_COLUMN: Record<TranslationTable, string> = {
|
||
dish_translations: "dish_id",
|
||
section_translations: "section_key",
|
||
allergen_translations: "allergen_code",
|
||
additive_translations: "additive_code",
|
||
};
|
||
|
||
/** Alle Übersetzungen einer Tabelle, gruppiert nach Eltern-ID. */
|
||
export async function loadTranslations(
|
||
table: TranslationTable
|
||
): Promise<Map<string, Record<string, string>>> {
|
||
const idCol = ID_COLUMN[table];
|
||
const rows = await query<{ id: string; language_code: string; name: string }>(
|
||
`select ${idCol} as id, language_code, name from ${table}`
|
||
);
|
||
return groupByParent(rows);
|
||
}
|
||
|
||
/** Übersetzungen nur für eine Teilmenge von Eltern-IDs laden (z. B. die
|
||
* Gerichte des aktuell angezeigten Wochenplan-Zeitraums). */
|
||
export async function loadTranslationsFor(
|
||
table: TranslationTable,
|
||
ids: string[]
|
||
): Promise<Map<string, Record<string, string>>> {
|
||
if (ids.length === 0) return new Map();
|
||
const idCol = ID_COLUMN[table];
|
||
const rows = await query<{ id: string; language_code: string; name: string }>(
|
||
`select ${idCol} as id, language_code, name from ${table} where ${idCol} = any($1)`,
|
||
[ids]
|
||
);
|
||
return groupByParent(rows);
|
||
}
|
||
|
||
function groupByParent(
|
||
rows: { id: string; language_code: string; name: string }[]
|
||
): Map<string, Record<string, string>> {
|
||
const map = new Map<string, Record<string, string>>();
|
||
for (const r of rows) {
|
||
if (!map.has(r.id)) map.set(r.id, {});
|
||
map.get(r.id)![r.language_code] = r.name;
|
||
}
|
||
return map;
|
||
}
|
||
|
||
/** Übersetzungen eines Eintrags für die übergebenen Sprachen setzen –
|
||
* ein leerer/nur-Leerzeichen-Wert löscht die Zeile für diese Sprache
|
||
* wieder (z. B. wenn ein Formularfeld geleert wird), statt eine leere
|
||
* Übersetzung zu speichern. */
|
||
export async function replaceTranslations(
|
||
table: TranslationTable,
|
||
id: string,
|
||
translations: Record<string, string | null | undefined>
|
||
): Promise<void> {
|
||
const idCol = ID_COLUMN[table];
|
||
for (const [languageCode, rawValue] of Object.entries(translations)) {
|
||
const value = rawValue?.trim();
|
||
if (!value) {
|
||
await query(`delete from ${table} where ${idCol} = $1 and language_code = $2`, [
|
||
id,
|
||
languageCode,
|
||
]);
|
||
continue;
|
||
}
|
||
await query(
|
||
`insert into ${table} (${idCol}, language_code, name) values ($1, $2, $3)
|
||
on conflict (${idCol}, language_code) do update set name = excluded.name`,
|
||
[id, languageCode, value]
|
||
);
|
||
}
|
||
}
|