Translation System Documentation
Overview
The translation system has been refactored to be centralized, reusable, and easy to understand. It can be used for any module that needs multi-language support (News, Events, Pages, etc.).
Architecture
1. useTranslationManager Hook (app/hooks/useTranslationManager.hook.ts)
Centralized hook that manages all translation logic:
Features:
- Reads
langandrefquery parameters for creating translations - Fetches French reference document when editing a translation
- Fetches all translations when editing French document
- Manages translation state (IDs, slugs, data)
- Provides helper functions for building payloads and URLs
Usage:
const translation = useTranslationManager({
api: News, // Your API service
moduleId: "module-id",
moduleSlug: "actus",
documentData: newsData, // Current document (when editing)
isNew: isNewNews,
});
// Access state
translation.currentLanguage // "fr" | "en" | "es"
translation.referenceId // French document ID
translation.translationSlugs // { en: "slug-en", es: "slug-es" }
translation.frenchSlug // French document slug
// Check if creating translation
if (translation.isCreatingTranslation) {
// Use langParam and refParam
}
// Build URLs
translation.getEditUrl(slug) // "/modules/actus/modifier/slug"
translation.getCreateUrl("en", refId) // "/modules/actus/créer?lang=en&ref=refId"
2. TranslationTabs Component (app/components/molecules/translationTabs.molecule.tsx)
Reusable component that displays language flags with navigation:
Features:
- Shows all active language flags
- Existing translations → link to edit
- Missing translations → link to create
- French flag → link back to original when editing translation
- Current language → highlighted, no link
Usage:
<TranslationTabs
currentLanguage="fr"
moduleSlug="actus"
frenchSlug="my-news-slug"
translationSlugs={{ en: "my-news-en", es: "my-news-es" }}
referenceId="french-doc-id"
isNew={false}
>
<YourFormContent />
</TranslationTabs>
3. LanguageTabs Component (app/components/molecules/languageTabs.molecule.tsx)
Low-level component that renders the flag UI:
Features:
- Vertical flag layout with connecting lines
- Supports both Link (navigation) and button (callback) modes
- Highlights active language
- Accessible (aria labels, keyboard navigation)
How It Works
Creating a New Translation
- User clicks on a missing language flag (e.g., 🇪🇸)
- Navigate to:
/modules/actus/créer?lang=es&ref=68ff3575e2e59b58c9788b6f useTranslationManagerreads query params:langParam= "es"refParam= "68ff3575e2e59b58c9788b6f" (French document ID)
- Form is initialized with:
translation: {
lang: "es",
ref_item: "68ff3575e2e59b58c9788b6f"
} - On submit, translation is created with correct language and reference
Editing an Existing Translation
- User clicks on existing language flag (e.g., 🇬🇧)
- Navigate to:
/modules/actus/modifier/my-news-en - Document is loaded by slug
useTranslationManagerdetects:currentLanguage= "en" (fromdocumentData.translation.lang)referenceId= French document ID (fromdocumentData.translation.ref_item)
- French document is fetched to get
frenchSlug - All sibling translations are fetched
- Flags are displayed with correct navigation URLs
Viewing All Translations (French Document)
- User edits French document:
/modules/actus/modifier/my-news-fr useTranslationManagerdetects:currentLanguage= "fr"referenceId= current document ID
- All translations are fetched (search by
translation.ref_item) - Flags are displayed:
- 🇫🇷 FR (active, no link)
- 🇬🇧 EN (link to edit if exists, or link to create)
- 🇪🇸 ES (link to edit if exists, or link to create)
Integration Guide
For a New Module
-
Create your API service with required methods:
class MyModuleAPI extends BaseAPI {
async getById(id: string) { ... }
async search(params: any) { ... }
} -
Use the hook in your form component:
const translation = useTranslationManager({
api: MyModuleAPI,
moduleId: moduleId!,
moduleSlug: moduleSlug!,
documentData: myData,
isNew: isNewDocument,
}); -
Wrap your form with TranslationTabs:
<TranslationTabs
currentLanguage={translation.currentLanguage}
moduleSlug={moduleSlug!}
frenchSlug={translation.frenchSlug}
translationSlugs={translation.translationSlugs}
referenceId={translation.referenceId}
isNew={isNew}
>
<YourFormFields />
</TranslationTabs> -
Handle form submission:
const onSubmit = (data: FormData) => {
if (translation.isCreatingTranslation) {
// Creating a translation
const payload = {
...data,
translation: translation.buildTranslationPayload(
translation.langParam!,
translation.refParam
),
};
createMutation.mutate(payload);
} else {
// Creating French document or updating
// ... your logic
}
};
Benefits
✅ Centralized Logic - All translation logic in one place
✅ Reusable - Works for any module (News, Events, Pages, etc.)
✅ Type-Safe - Full TypeScript support
✅ Easy to Understand - Clear separation of concerns
✅ Maintainable - Changes in one place affect all modules
✅ Consistent UX - Same behavior across all modules
URL Structure
- Create French:
/modules/{slug}/créer - Create Translation:
/modules/{slug}/créer?lang={lang}&ref={refId} - Edit:
/modules/{slug}/modifier/{documentSlug}
State Management
The hook manages these states internally:
currentLanguage- Current editing languagereferenceId- French document IDtranslations- All translation datatranslationIds- Translation document IDstranslationSlugs- Translation slugs for navigationfrenchSlug- French document slug
Future Improvements
- Add loading states for translation fetching
- Add error handling for failed translation fetches
- Add support for copying content from one language to another
- Add translation progress indicator (e.g., "2/3 languages translated")