improve translation with hardcoded terminology dictionary and British English
All checks were successful
CI / update (push) Successful in 9s
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:
@@ -14,6 +14,74 @@ const CATEGORY_TRANSLATIONS: Record<string, string> = {
|
|||||||
"Snack": "Snack"
|
"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 {
|
interface DeepLResponse {
|
||||||
translations: Array<{
|
translations: Array<{
|
||||||
detected_source_language: string;
|
detected_source_language: string;
|
||||||
@@ -46,13 +114,13 @@ class DeepLTranslationService {
|
|||||||
/**
|
/**
|
||||||
* Translate a single text string
|
* Translate a single text string
|
||||||
* @param text - The text to translate
|
* @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
|
* @param preserveFormatting - Whether to preserve HTML/formatting
|
||||||
* @returns Translated text
|
* @returns Translated text
|
||||||
*/
|
*/
|
||||||
async translateText(
|
async translateText(
|
||||||
text: string | null | undefined,
|
text: string | null | undefined,
|
||||||
targetLang: string = 'EN',
|
targetLang: string = 'EN-GB',
|
||||||
preserveFormatting: boolean = false
|
preserveFormatting: boolean = false
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Return empty string for null, undefined, or empty strings
|
// Return empty string for null, undefined, or empty strings
|
||||||
@@ -86,7 +154,10 @@ class DeepLTranslationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data: DeepLResponse = await response.json();
|
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) {
|
} catch (error) {
|
||||||
console.error('Translation error:', error);
|
console.error('Translation error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -97,12 +168,12 @@ class DeepLTranslationService {
|
|||||||
* Translate multiple texts in a single batch request
|
* Translate multiple texts in a single batch request
|
||||||
* More efficient than individual calls
|
* More efficient than individual calls
|
||||||
* @param texts - Array of texts to translate
|
* @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)
|
* @returns Array of translated texts (preserves empty strings in original positions)
|
||||||
*/
|
*/
|
||||||
async translateBatch(
|
async translateBatch(
|
||||||
texts: string[],
|
texts: string[],
|
||||||
targetLang: string = 'EN'
|
targetLang: string = 'EN-GB'
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
if (!texts.length) {
|
if (!texts.length) {
|
||||||
return [];
|
return [];
|
||||||
@@ -155,13 +226,16 @@ class DeepLTranslationService {
|
|||||||
const data: DeepLResponse = await response.json();
|
const data: DeepLResponse = await response.json();
|
||||||
const translatedTexts = data.translations.map(t => t.text);
|
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
|
// Map translated texts back to original positions, preserving empty strings
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
let translatedIndex = 0;
|
let translatedIndex = 0;
|
||||||
|
|
||||||
for (let i = 0; i < texts.length; i++) {
|
for (let i = 0; i < texts.length; i++) {
|
||||||
if (nonEmptyIndices.includes(i)) {
|
if (nonEmptyIndices.includes(i)) {
|
||||||
result.push(translatedTexts[translatedIndex]);
|
result.push(processedTexts[translatedIndex]);
|
||||||
translatedIndex++;
|
translatedIndex++;
|
||||||
} else {
|
} else {
|
||||||
result.push(''); // Keep empty string
|
result.push(''); // Keep empty string
|
||||||
@@ -340,10 +414,10 @@ class DeepLTranslationService {
|
|||||||
result.description = await this.translateText(recipe.description);
|
result.description = await this.translateText(recipe.description);
|
||||||
break;
|
break;
|
||||||
case 'preamble':
|
case 'preamble':
|
||||||
result.preamble = await this.translateText(recipe.preamble || '', 'EN', true);
|
result.preamble = await this.translateText(recipe.preamble || '', 'EN-GB', true);
|
||||||
break;
|
break;
|
||||||
case 'addendum':
|
case 'addendum':
|
||||||
result.addendum = await this.translateText(recipe.addendum || '', 'EN', true);
|
result.addendum = await this.translateText(recipe.addendum || '', 'EN-GB', true);
|
||||||
break;
|
break;
|
||||||
case 'note':
|
case 'note':
|
||||||
result.note = await this.translateText(recipe.note || '');
|
result.note = await this.translateText(recipe.note || '');
|
||||||
|
|||||||
Reference in New Issue
Block a user