diff --git a/crates/miroir-proxy/src/routes/search_ui.rs b/crates/miroir-proxy/src/routes/search_ui.rs index 2f120c9..df043e9 100644 --- a/crates/miroir-proxy/src/routes/search_ui.rs +++ b/crates/miroir-proxy/src/routes/search_ui.rs @@ -89,6 +89,9 @@ pub struct SearchUiIndexConfig { /// Thumbnail field for images #[serde(skip_serializing_if = "Option::is_none")] pub thumbnail_field: Option, + /// i18n locales (lang code -> translation object) for operator-supplied translations (plan §13.21) + #[serde(skip_serializing_if = "Option::is_none")] + pub locales: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -458,6 +461,7 @@ pub async fn get_config( primary_key_field: None, hit_url_template: None, thumbnail_field: None, + locales: None, })) } diff --git a/docs/search_ui_i18n.md b/docs/search_ui_i18n.md new file mode 100644 index 0000000..678ea5c --- /dev/null +++ b/docs/search_ui_i18n.md @@ -0,0 +1,93 @@ +# Search UI i18n (Internationalization) + +## Overview + +The Search UI supports internationalization (i18n) for operator-supplied translations. This is part of plan §13.21. + +## How It Works + +1. **Query Parameter**: Users can specify a language via the `lang` query parameter: + - Example: `GET /ui/search/myindex?lang=fr` + +2. **Browser Fallback**: If no `lang` parameter is provided, the UI uses `navigator.language` + +3. **Fallback to English**: If the requested language is not configured, the UI falls back to English (the default built-in locale) + +## Configuring Custom Locales + +Operators can add custom translations via the `POST /_miroir/ui/search/{index}/config` endpoint: + +```bash +curl -X POST "http://miroir:8080/_miroir/ui/search/myindex/config" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "locales": { + "fr": { + "ui.title": "Recherche", + "ui.logo": "Recherche", + "ui.search.placeholder": "Rechercher...", + "ui.search.label": "Recherche", + "ui.search.ariaLabel": "Requête de recherche", + "ui.search.hint": "Tapez pour rechercher. Appuyez sur / pour focus. Utilisez les flèches pour naviguer.", + "ui.search.button": "Rechercher", + "ui.filters": "Filtres", + "ui.sort.label": "Trier:", + "ui.sort.relevance": "Pertinence", + "ui.perPage.label": "Par page:", + "ui.results.empty": "Aucun résultat trouvé", + "ui.results.emptyHint": "Essayez d'ajuster votre recherche ou vos filtres", + "ui.results.count": "{count} résultats ({time}ms)" + } + } + }' +``` + +## Built-in Translations + +The Search UI includes English translations by default in the bundled JavaScript. The `t()` function handles variable interpolation: + +```javascript +// With interpolation +t('ui.results.count', { count: 42, time: 15 }) +// Returns: "42 results (15ms)" + +// With French locale +// Returns: "42 résultats (15ms)" +``` + +## Translation Keys + +The following translation keys are supported (with default English values): + +### UI Labels +- `ui.title` - Page title +- `ui.logo` - Logo text +- `ui.search.placeholder` - Search input placeholder +- `ui.search.label` - Search label +- `ui.search.ariaLabel` - Search ARIA label +- `ui.search.hint` - Search keyboard hint +- `ui.search.button` - Search button ARIA label +- `ui.darkMode.ariaLabel` - Dark mode toggle ARIA label +- `ui.filters` - Filters button text +- `ui.sort.label` - Sort selector label +- `ui.sort.relevance` - Default sort option text +- `ui.perPage.label` - Per-page selector label + +### Results +- `ui.results.empty` - Empty state title +- `ui.results.emptyHint` - Empty state hint +- `ui.results.didYouMean` - "Did you mean" suggestion (use `{query}` variable) +- `ui.results.count` - Result count (use `{count}` and `{time}` variables) + +### Errors +- `ui.error.noIndex` - No index specified error +- `ui.error.searchFailed` - Search failed error (use `{status}` variable) +- `ui.error.initFailed` - Init failed error (use `{message}` variable) +- `ui.error.sessionFailed` - Session fetch failed + +### Accessibility +- `ui.filters.ariaLabel` - Filters ARIA label +- `ui.pagination.ariaLabel` - Pagination ARIA label +- `ui.keyboardShortcuts.ariaLabel` - Keyboard shortcuts ARIA label +- `ui.keyboardShortcuts.text` - Keyboard shortcuts help text