improve translation with hardcoded terminology dictionary and British English
All checks were successful
CI / update (push) Successful in 9s

Add ingredient terminology dictionaries to override DeepL translations for consistent cooking terminology:
- German cooking terms (EL→tbsp, TL→tsp, Ei→egg, etc.)
- US to British English conversions (zucchini→courgette, eggplant→aubergine, etc.)

Change DeepL target language from EN to EN-GB to force British English translations.

Apply post-processing to all translated text to ensure terminology consistency.
This commit is contained in:
2025-12-27 13:43:24 +01:00
parent d4564ca973
commit 0d01e595ea

View File

@@ -14,6 +14,74 @@ const CATEGORY_TRANSLATIONS: Record<string, string> = {
"Snack": "Snack"
};
// Ingredient terminology dictionary - German cooking terms to British English
// These override DeepL translations for consistent terminology
const INGREDIENT_TERMINOLOGY: Record<string, string> = {
// Measurement abbreviations
"EL": "tbsp",
"TL": "tsp",
"Msp": "pinch",
"Prise": "pinch",
"Zweig": "twig",
"Zweige": "twigs",
"Bund": "bunch",
// Common ingredients
"Ei": "egg",
"Öl": "Oil",
"Backen": "Baking",
};
// US English to British English food terminology
// Applied after DeepL translation to ensure British English
const US_TO_BRITISH_ENGLISH: Record<string, string> = {
"zucchini": "courgette",
"zucchinis": "courgettes",
"eggplant": "aubergine",
"eggplants": "aubergines",
"cilantro": "coriander",
"arugula": "rocket",
"rutabaga": "swede",
"rutabagas": "swedes",
"bell pepper": "pepper",
"bell peppers": "peppers",
"scallion": "spring onion",
"scallions": "spring onions",
"green onion": "spring onion",
"green onions": "spring onions",
};
/**
* Apply ingredient terminology replacements to translated text
* Handles both German terms that may have slipped through DeepL
* and US English to British English conversions
* @param text - The translated text to process
* @returns Text with terminology replacements applied
*/
function applyIngredientTerminology(text: string): string {
if (!text) return text;
let result = text;
// First pass: Replace any remaining German terms with British English
// Using word boundaries to avoid partial matches
Object.entries(INGREDIENT_TERMINOLOGY).forEach(([german, english]) => {
// Case-insensitive replacement with word boundaries
const regex = new RegExp(`\\b${german}\\b`, 'gi');
result = result.replace(regex, english);
});
// Second pass: Replace US English terms with British English
// More careful here to handle both whole words and phrases
Object.entries(US_TO_BRITISH_ENGLISH).forEach(([us, british]) => {
// Case-insensitive replacement with word boundaries
const regex = new RegExp(`\\b${us}\\b`, 'gi');
result = result.replace(regex, british);
});
return result;
}
interface DeepLResponse {
translations: Array<{
detected_source_language: string;
@@ -46,13 +114,13 @@ class DeepLTranslationService {
/**
* Translate a single text string
* @param text - The text to translate
* @param targetLang - Target language code (default: 'EN')
* @param targetLang - Target language code (default: 'EN-GB' for British English)
* @param preserveFormatting - Whether to preserve HTML/formatting
* @returns Translated text
*/
async translateText(
text: string | null | undefined,
targetLang: string = 'EN',
targetLang: string = 'EN-GB',
preserveFormatting: boolean = false
): Promise<string> {
// Return empty string for null, undefined, or empty strings
@@ -86,7 +154,10 @@ class DeepLTranslationService {
}
const data: DeepLResponse = await response.json();
return data.translations[0]?.text || '';
const translatedText = data.translations[0]?.text || '';
// Apply ingredient terminology replacements for British English
return applyIngredientTerminology(translatedText);
} catch (error) {
console.error('Translation error:', error);
throw error;
@@ -97,12 +168,12 @@ class DeepLTranslationService {
* Translate multiple texts in a single batch request
* More efficient than individual calls
* @param texts - Array of texts to translate
* @param targetLang - Target language code
* @param targetLang - Target language code (default: 'EN-GB' for British English)
* @returns Array of translated texts (preserves empty strings in original positions)
*/
async translateBatch(
texts: string[],
targetLang: string = 'EN'
targetLang: string = 'EN-GB'
): Promise<string[]> {
if (!texts.length) {
return [];
@@ -155,13 +226,16 @@ class DeepLTranslationService {
const data: DeepLResponse = await response.json();
const translatedTexts = data.translations.map(t => t.text);
// Apply ingredient terminology replacements for British English
const processedTexts = translatedTexts.map(text => applyIngredientTerminology(text));
// Map translated texts back to original positions, preserving empty strings
const result: string[] = [];
let translatedIndex = 0;
for (let i = 0; i < texts.length; i++) {
if (nonEmptyIndices.includes(i)) {
result.push(translatedTexts[translatedIndex]);
result.push(processedTexts[translatedIndex]);
translatedIndex++;
} else {
result.push(''); // Keep empty string
@@ -340,10 +414,10 @@ class DeepLTranslationService {
result.description = await this.translateText(recipe.description);
break;
case 'preamble':
result.preamble = await this.translateText(recipe.preamble || '', 'EN', true);
result.preamble = await this.translateText(recipe.preamble || '', 'EN-GB', true);
break;
case 'addendum':
result.addendum = await this.translateText(recipe.addendum || '', 'EN', true);
result.addendum = await this.translateText(recipe.addendum || '', 'EN-GB', true);
break;
case 'note':
result.note = await this.translateText(recipe.note || '');