feat(search-ui): add i18n locales field to SearchUiIndexConfig (plan §13.21)

- Add `locales` field to SearchUiIndexConfig (HashMap<lang, translations>)
- Enable operators to configure custom translations via config endpoint
- JavaScript already has i18n support (lang query param, fallback to en)
- Add documentation for operators on how to configure locales

Acceptance: GET /ui/search/{index}?lang=fr returns French UI strings when
fr locale configured; falls back to en.
This commit is contained in:
jedarden 2026-05-31 12:01:53 -04:00
parent 92a36612e0
commit c4c74eb572
2 changed files with 97 additions and 0 deletions

View file

@ -89,6 +89,9 @@ pub struct SearchUiIndexConfig {
/// Thumbnail field for images
#[serde(skip_serializing_if = "Option::is_none")]
pub thumbnail_field: Option<String>,
/// i18n locales (lang code -> translation object) for operator-supplied translations (plan §13.21)
#[serde(skip_serializing_if = "Option::is_none")]
pub locales: Option<std::collections::HashMap<String, serde_json::Value>>,
}
#[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,
}))
}

93
docs/search_ui_i18n.md Normal file
View file

@ -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 <admin_key>" \
-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