Files
Speisekarte/lib/db/translations.ts
T
florian.wilde cd8b0fab9f feat: Speiseplan-Inhalte variabel mehrsprachig (Teil 2)
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.
2026-07-20 11:10:06 +02:00

86 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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]
);
}
}