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.
This commit is contained in:
+68
-39
@@ -1,39 +1,70 @@
|
||||
import "server-only";
|
||||
import { query, queryOne, withTransaction } from "@/lib/db";
|
||||
import { query, queryOne } from "@/lib/db";
|
||||
import {
|
||||
loadTranslations,
|
||||
loadTranslationsFor,
|
||||
replaceTranslations,
|
||||
type TranslationTable,
|
||||
} from "./translations";
|
||||
import type { CodeItem } from "@/lib/types";
|
||||
|
||||
// "allergens"/"additives" sind identisch aufgebaute Tabellen (Kürzel +
|
||||
// Bezeichnung in DE/EN/PL). Tabellenname kommt nie aus Nutzereingaben,
|
||||
// Bezeichnung in Deutsch, Übersetzungen in der jeweiligen
|
||||
// *_translations-Tabelle). Tabellenname kommt nie aus Nutzereingaben,
|
||||
// sondern immer aus dem literal getypten Parameter unten – sicher für
|
||||
// String-Interpolation in der Query.
|
||||
export type CodeTable = "allergens" | "additives";
|
||||
|
||||
const TRANSLATION_TABLE: Record<CodeTable, TranslationTable> = {
|
||||
allergens: "allergen_translations",
|
||||
additives: "additive_translations",
|
||||
};
|
||||
|
||||
interface CodeRow {
|
||||
code: string;
|
||||
name: string;
|
||||
sort: number;
|
||||
}
|
||||
|
||||
export async function listCodes(table: CodeTable): Promise<CodeItem[]> {
|
||||
return query<CodeItem>(`select * from ${table} order by sort`);
|
||||
const rows = await query<CodeRow>(`select code, name, sort from ${table} order by sort`);
|
||||
const translations = await loadTranslations(TRANSLATION_TABLE[table]);
|
||||
return rows.map((r) => ({ ...r, translations: translations.get(r.code) ?? {} }));
|
||||
}
|
||||
|
||||
export async function createCode(
|
||||
table: CodeTable,
|
||||
input: { code: string; name: string; name_en: string | null; name_pl: string | null }
|
||||
input: { code: string; name: string; translations?: Record<string, string | null> }
|
||||
): Promise<CodeItem> {
|
||||
const maxRow = await queryOne<{ sort: number }>(
|
||||
`select sort from ${table} order by sort desc limit 1`
|
||||
);
|
||||
return (await queryOne<CodeItem>(
|
||||
`insert into ${table} (code, name, name_en, name_pl, sort) values ($1, $2, $3, $4, $5) returning *`,
|
||||
[input.code, input.name, input.name_en, input.name_pl, (maxRow?.sort ?? 0) + 1]
|
||||
const row = (await queryOne<CodeRow>(
|
||||
`insert into ${table} (code, name, sort) values ($1, $2, $3) returning code, name, sort`,
|
||||
[input.code, input.name, (maxRow?.sort ?? 0) + 1]
|
||||
))!;
|
||||
if (input.translations) {
|
||||
await replaceTranslations(TRANSLATION_TABLE[table], row.code, input.translations);
|
||||
}
|
||||
const translations = await loadTranslationsFor(TRANSLATION_TABLE[table], [row.code]);
|
||||
return { ...row, translations: translations.get(row.code) ?? {} };
|
||||
}
|
||||
|
||||
export async function updateCode(
|
||||
table: CodeTable,
|
||||
originalCode: string,
|
||||
input: { code: string; name: string; name_en: string | null; name_pl: string | null }
|
||||
input: { code: string; name: string; translations?: Record<string, string | null> }
|
||||
): Promise<CodeItem | null> {
|
||||
return queryOne<CodeItem>(
|
||||
`update ${table} set code = $1, name = $2, name_en = $3, name_pl = $4 where code = $5 returning *`,
|
||||
[input.code, input.name, input.name_en, input.name_pl, originalCode]
|
||||
const row = await queryOne<CodeRow>(
|
||||
`update ${table} set code = $1, name = $2 where code = $3 returning code, name, sort`,
|
||||
[input.code, input.name, originalCode]
|
||||
);
|
||||
if (!row) return null;
|
||||
if (input.translations) {
|
||||
await replaceTranslations(TRANSLATION_TABLE[table], row.code, input.translations);
|
||||
}
|
||||
const translations = await loadTranslationsFor(TRANSLATION_TABLE[table], [row.code]);
|
||||
return { ...row, translations: translations.get(row.code) ?? {} };
|
||||
}
|
||||
|
||||
export async function deleteCode(table: CodeTable, code: string): Promise<void> {
|
||||
@@ -43,8 +74,7 @@ export async function deleteCode(table: CodeTable, code: string): Promise<void>
|
||||
export interface CodeImportRow {
|
||||
code: string;
|
||||
name: string;
|
||||
name_en: string | null;
|
||||
name_pl: string | null;
|
||||
translations: Record<string, string>;
|
||||
}
|
||||
|
||||
/** CSV-Import: legt neue Kürzel an, bestehende (gleicher Code) werden
|
||||
@@ -54,32 +84,31 @@ export async function importCodes(
|
||||
table: CodeTable,
|
||||
rows: CodeImportRow[]
|
||||
): Promise<{ created: number; updated: number }> {
|
||||
return withTransaction(async (client) => {
|
||||
const existing = await client.query<{ code: string }>(`select code from ${table}`);
|
||||
const known = new Set(existing.rows.map((r) => r.code));
|
||||
const maxRow = await client.query<{ sort: number }>(
|
||||
`select coalesce(max(sort), 0) as sort from ${table}`
|
||||
);
|
||||
let nextSort = (maxRow.rows[0]?.sort ?? 0) + 1;
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
const existing = await query<{ code: string }>(`select code from ${table}`);
|
||||
const known = new Set(existing.map((r) => r.code));
|
||||
const maxRow = await queryOne<{ sort: number }>(
|
||||
`select coalesce(max(sort), 0) as sort from ${table}`
|
||||
);
|
||||
let nextSort = (maxRow?.sort ?? 0) + 1;
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
if (known.has(row.code)) {
|
||||
await client.query(
|
||||
`update ${table} set name = $1, name_en = $2, name_pl = $3 where code = $4`,
|
||||
[row.name, row.name_en, row.name_pl, row.code]
|
||||
);
|
||||
updated++;
|
||||
} else {
|
||||
await client.query(
|
||||
`insert into ${table} (code, name, name_en, name_pl, sort) values ($1, $2, $3, $4, $5)`,
|
||||
[row.code, row.name, row.name_en, row.name_pl, nextSort++]
|
||||
);
|
||||
known.add(row.code);
|
||||
created++;
|
||||
}
|
||||
for (const row of rows) {
|
||||
if (known.has(row.code)) {
|
||||
await query(`update ${table} set name = $1 where code = $2`, [row.name, row.code]);
|
||||
updated++;
|
||||
} else {
|
||||
await query(`insert into ${table} (code, name, sort) values ($1, $2, $3)`, [
|
||||
row.code,
|
||||
row.name,
|
||||
nextSort++,
|
||||
]);
|
||||
known.add(row.code);
|
||||
created++;
|
||||
}
|
||||
return { created, updated };
|
||||
});
|
||||
if (Object.keys(row.translations).length > 0) {
|
||||
await replaceTranslations(TRANSLATION_TABLE[table], row.code, row.translations);
|
||||
}
|
||||
}
|
||||
return { created, updated };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user