goblin-nabu-store est le module de persistance et de gestion des données de traduction du framework Xcraft. Il constitue la couche de stockage de l'écosystème Nabu, en fournissant les entités RethinkDB pour les locales, les messages et les traductions, ainsi que les interfaces d'administration (workitems, datagrid, wizards) permettant de gérer ces données depuis l'interface graphique. Il expose également les utilitaires de construction de résumés multi-langues utilisés par d'autres modules pour indexer du contenu traduisible.
Le module s'appuie sur goblin-workshop pour générer automatiquement les services CRUD des entités, sur goblin-elasticsearch pour l'indexation full-text, et sur goblin-nabu pour la résolution des traductions.
- Structure du module
- Fonctionnement global
- Exemples d'utilisation
- Interactions avec d'autres modules
- Détails des sources
- Licence
Le module est organisé en quatre couches :
- Acteur principal (
lib/service.js) : Goblin singletonnabu-storequi orchestre l'initialisation du storage, la gestion des locales et la synchronisation avec Elasticsearch. - Entités (
entities/) : Définitions des trois entités persistées viagoblin-workshop—locale,nabuMessageetnabuTranslation. - Utilitaires de résumés (
lib/summaries.js,lib/summaries/) : Pipeline de construction de résumés multi-langues à partir de données contenant des chaînes traduisibles. - Interfaces utilisateur (
widgets/) : Workitems, datagrid, wizards et leurs services associés exposés sur le bus Xcraft.
Au démarrage, nabu-store suit un flux d'initialisation en deux phases :
1. BOOT (singleton, appelé une fois)
Configuration workshop (mandate 'nabu', RethinkDB, Elasticsearch)
→ workshop.init
↓
2. INIT (appelé par l'application hôte avec sa configuration)
Vérification des paramètres requis (desktopId, configuration)
→ ensureEntities : création des tables RethinkDB (locale, nabuMessage, nabuTranslation)
→ loadLocales : chargement des locales publiées
→ handleElasticIndexes : création d'un index Elasticsearch par locale
→ startQuestOnChanges : écoute des changements de la table 'locale' en temps réel
Lorsque goblin-workshop est configuré avec enableMultiLanguageIndex: true, nabu-store crée un index Elasticsearch par locale active. La convention de nommage est {mandate}-{localeName} (ex : myapp-fr_ch, myapp-de_ch). Ces indexes sont recréés dynamiquement à chaque modification de la liste des locales (via le listener RethinkDB handle-locales-change).
Le point d'entrée principal (lib/summaries.js) est utilisé par les modules consommateurs pour construire des résumés traduits dans toutes les locales disponibles :
summaries (objet clé/valeur avec valeurs nabu ou primitives)
↓
dereferenceSummaries : résolution des références @{ref} dans les translatableMarkdown
↓
buildMultiLanguageObject : résolution de chaque champ pour chaque locale connue
↓
joinSummaries : aplatissement des translatableString en chaînes simples
↓
{ '_original': {...}, 'fr_CH': {...}, 'de_CH': {...}, ... }
Les trois entités sont construites avec goblin-workshop via buildEntity :
| Entité | Statut initial | Relations | Usage |
|---|---|---|---|
locale |
draft |
— | Définit une langue disponible dans l'application |
nabuMessage |
published |
— | Représente un message source identifié par son nabuId |
nabuTranslation |
draft |
messageId → nabuMessage |
Contient le texte traduit d'un message pour une locale |
nabu-store est un acteur Goblin (non Elf), son initialisation se fait via le système quest :
// Initialiser nabu-store avec la configuration de l'application
await quest.cmd('nabu-store.init', {
desktopId: 'myapp@desktop',
configuration: {
mandate: 'myapp',
elasticsearchUrl: 'http://127.0.0.1:9200',
rethinkdbHost: '127.0.0.1',
useNabu: true,
mainGoblin: 'nabu-store',
defaultContextId: 'nabu',
},
});// Obtenir la configuration stockée pour un mandate donné
const config = await quest.cmd('nabu-store.get-configuration', {
mandate: 'myapp',
});const buildMultiLanguageSummaries = require('goblin-nabu-store');
const T = require('goblin-nabu/widgets/helpers/t.js');
// Dans une quête Goblin, construire les résumés traduits d'une entité
const summaries = {
info: T("Titre de l'entité"),
description: 'Texte statique',
};
const multiLangSummaries = await buildMultiLanguageSummaries(
quest,
summaries,
false // fromCache: lire depuis les entités nabuTranslation
);
// → { '_original': { info: "Titre de l'entité", description: 'Texte statique' },
// 'fr_CH': { info: "Titre de l'entité", description: 'Texte statique' },
// 'de_CH': { info: 'Titel der Entität', description: 'Texte statique' } }// Forcer la recréation des indexes (utile après ajout de locales)
await quest.cmd('nabu-store.handle-elastic-indexes', {
locales: [{name: 'fr_CH'}, {name: 'de_CH'}],
mandate: 'myapp',
force: true,
});- goblin-nabu : Fournit les helpers
T(), la fonctionTr()et les composants de traduction utilisés dans les widgets et les utilitaires de résumés. - goblin-workshop : Génère automatiquement les services CRUD pour les entités (
buildEntity,buildWorkitem,buildWizard) et orchestre l'initialisation du backend de données. - goblin-desktop : Fournit
buildWizardpour les assistants de création de locale et d'import. - goblin-elasticsearch : Fournit
buildHinterpour les recherches full-text sur les locales et messages (conditionnel ànabuConfig.storageAvailable). - xcraft-core-goblin : Mécanismes de base Goblin (quêtes, dispatch, createSingle, bus Xcraft).
- xcraft-core-etc : Chargement de la configuration
goblin-workshopetgoblin-nabu. - xcraft-core-shredder : Manipulation des structures Immutable.js dans
dereference.js.
Cœur du module. Orchestre l'initialisation du storage, la gestion des tables RethinkDB, la synchronisation des indexes Elasticsearch et l'écoute des changements de locales en temps réel.
{
id: 'nabu-store';
// Pas d'état Redux explicite — les données opérationnelles
// sont stockées via quest.goblin.setX() :
// - configuration : config du boot (mandate 'nabu')
// - configuration.<mandate> : config par mandate (définie à l'init)
}nabu-store est un singleton (Goblin.createSingle). Il expose deux quêtes de démarrage :
boot: appelée une seule fois au démarrage du framework. Configure la connexion workshop pour le mandate interne'nabu'et initialise workshop.init: appelée par l'application hôte avec sa configuration propre. Vérifie les paramètres, crée les tables et indexes, charge les locales, et souscrit au changefeed RethinkDB sur la tablelocale.
La quête dispose assure la fermeture propre en arrêtant l'écoute des changements.
boot()— Initialise la connexion workshop avec la configuration interne du mandate'nabu'. Appelée automatiquement au démarrage.init(desktopId, configuration)— Initialise le storage pour un mandate applicatif. Lance la création des tables, le chargement des locales et l'abonnement aux changements RethinkDB. Lève une erreur sidesktopIdouconfigurationsont absents.ensure-entities(desktopId)— Crée (si nécessaire) les tableslocale,nabuMessageetnabuTranslationdans RethinkDB, ainsi que leurs indexes.load-locales(desktopId)— Lit toutes les locales au statutpublisheddepuis RethinkDB et émet l'événementlocales-loadedavec la liste.handle-elastic-indexes(locales, mandate, configuration, force)— Crée ou met à jour les indexes Elasticsearch multi-langues ({mandate}-{localeName}). NécessiteenableMultiLanguageIndex: truedans la config workshop.handle-locales-change(change, desktopId, mandate)— Réagit aux modifications de la tablelocale(ajout, suppression). Recharge les locales et reconfigure les indexes Elasticsearch. Les changements de typestatesont ignorés.stop-listen-locales(desktopId)— Arrête le listener RethinkDB sur la tablelocale.get-configuration(mandate)— Retourne la configuration stockée pour un mandate donné.dispose(desktopId)— Ferme proprement les ressources (appellestopListenLocales).
Point d'entrée principal du module (package.json → "main": "lib/summaries.js"). Exporte la fonction buildMultiLanguageSummaries consommée par les autres modules pour construire des résumés traduits dans toutes les locales.
buildMultiLanguageSummaries(quest, summaries, fromCache)— Générateur watt. Prend un objet de résumés (valeurs pouvant être des primitives, objets nabu, ou tableaux), déréférence lestranslatableMarkdown, résout chaque champ pour chaque locale connue, et retourne un objet indexé par nom de locale (plus_original). Retournesummariesinchangé si la valeur est falsy. Le paramètrefromCacheest passé àTrpour choisir entre lecture du cache packagé ou lecture depuis la base.
Pré-traitement des résumés de type translatableMarkdown avant traduction. Un translatableMarkdown est un objet de la forme :
{
_type: 'translatableMarkdown',
_string: 'abc@{ref}xyz',
_refs: { ref: { /* objet nabu */ } }
}Le traitement se déroule en deux passes :
applySplitTransform: découpe la chaîne_stringen tableau d'alternances texte/référence (['abc', '@{ref}', 'xyz']).applyDereferenceTransform: remplace chaque@{ref}par la valeur correspondante dans_refs.
Les objets Shredder/Immutable sont préalablement convertis en JS natif via toJS(). Les champs non-translatableMarkdown sont retournés inchangés.
dereferenceSummaries(summaries)— Applique le déréférencement sur tous les champs d'un objet de résumés. Retourne un nouvel objet avec les mêmes clés.
Construit la version multi-langue d'un objet de résumés en appelant Tr (depuis goblin-nabu) pour chaque locale disponible dans le warehouse. Parcourt récursivement les tableaux et objets imbriqués pour traduire chaque objet nabu trouvé (reconnu à la présence d'un champ nabuId).
La locale spéciale _original est toujours générée en plus des locales réelles — elle conserve les nabuId bruts sans traduction. Les locales masquées (hide: true) sont exclues du résultat.
Entité locale construite avec buildEntity de goblin-workshop.
| Propriété | Type | Valeur par défaut | Description |
|---|---|---|---|
name |
string |
'' |
Identifiant technique de la locale (ex : fr_CH, fr_CH/valais) |
text |
string |
'' |
Nom commun affiché (ex : Français) |
description |
string |
'' |
Description libre |
Résumés : info et description sont tous deux alimentés par text (ou name si text est vide).
Indexation : le champ description des summaries est indexé dans Elasticsearch sous la clé info.
Création (onNew) : génère automatiquement un name de type locale-{uuid6} si aucun n'est fourni.
Entité nabuMessage représentant un message source identifié par son nabuId.
| Propriété | Type | Valeur par défaut | Description |
|---|---|---|---|
nabuId |
string |
'' |
Identifiant textuel du message (clé de traduction) |
custom |
bool |
false |
true si créé manuellement (non extrait du code) |
sources |
array |
[] |
Liste des emplacements dans le code source |
Résumés : info et description reprennent le nabuId.
Indexation : champs info (description) et value (description en minuscules, pour tri).
Statut initial : published (les messages sont immédiatement visibles).
Entité nabuTranslation liant un nabuMessage à une locale avec le texte traduit.
| Propriété | Type | Valeur par défaut | Description |
|---|---|---|---|
messageId |
entityId |
null |
Référence vers l'entité nabuMessage parente |
locale |
string |
'' |
Nom de la locale (ex : fr_CH) |
text |
string |
'' |
Texte traduit |
Résumés : info et description contiennent le texte traduit. localeName contient le nom de la locale.
Indexation : champs info, messageId, et un champ dynamique {localeName}-value (texte en minuscules) pour le tri et la recherche par locale.
Création (onNew) : si locale n'est pas fourni, il est extrait de l'id en prenant la partie après le premier @.
Grille d'administration des messages Nabu. Affiche l'ensemble des nabuMessage avec leurs traductions dans deux locales configurables simultanément.
Service Goblin généré par buildWorkitem avec le type datagrid. Colonnes : indicateur de traductions manquantes, identifiant du message (nabuId), deux colonnes de locale sélectionnables, et bouton d'ouverture en édition externe.
À la création (afterCreate), les deux premières locales disponibles dans le warehouse sont automatiquement sélectionnées pour les colonnes locale_1 et locale_2.
Quêtes spécifiques :
openSingleEntity(entityId, navigate)— Émet l'événement{toolbarId}.edit-message-requestedpour ouvrir le message dans l'éditeur Nabu.changeSelectedLocale(index, locale)— Change la locale affichée dans une colonne (index 2 ou 3). Réinitialise le tri si la colonne triée est modifiée.applyElasticVisualization(value, sort)— Applique une recherche full-text ou un tri sur les résultats de la datagrid. Sivalueest vide ou non fourni, bascule le tri ; sinon déclenche une recherche Elasticsearch.setNeedTranslation()— Interroge RethinkDB pour identifier les locales ayant des traductions manquantes (texte vide), et met à jourhasTranslationsdans l'état.
Rendu des en-têtes de colonnes. La colonne nabuId affiche un label fixe. Les colonnes locale_1 et locale_2 affichent un TextFieldCombo (HeaderCombo) permettant de choisir la locale parmi celles disponibles dans backend.nabu.locales. Un indicateur visuel (icône triangle d'avertissement) signale les locales ayant des traductions manquantes. Le combo déclenche setNeedTranslation à l'ouverture pour actualiser les indicateurs.
Rendu des cellules de chaque ligne. Utilise TranslationFieldConnected (depuis goblin-nabu) pour les colonnes de traduction, HighlightLabel pour la colonne nabuId (avec surbrillance des termes recherchés), et un Button crayon pour l'ouverture externe.
Deux composants Label connectés au store Redux :
InfoLabel— Affiche une icône d'information (regular/info-circle, si une description source existe) ou d'avertissement (solid/exclamation-triangle, si une traduction est manquante dans les colonnes visibles) selon l'état des traductions.SortLabel— Affiche l'icône de tri courante (neutresolid/sort, ascendantsolid/sort-alpha-upou descendantsolid/sort-alpha-down) selon l'état de tri de la datagrid. Masqué si une recherche textuelle est active.
Exports du rendu UI : headerCell, rowCell, sortCell et hinter. La zone hinter contient un champ de recherche full-text avec throttle à 200 ms, déclenchant applyElasticVisualization.
Formulaire d'édition d'un nabuMessage individuel avec prévisualisation des paramètres ICU.
Service Goblin de type workitem. À l'ouverture (onLoad), parse les paramètres ICU du nabuId via parseParameters (depuis goblin-nabu) et initialise icuParameters avec une entrée vide par paramètre trouvé. En cas d'erreur de parsing ICU, stocke le message d'erreur dans nabuIdIcuError. La quête changeIcuParameter met à jour dynamiquement les paramètres pour la prévisualisation.
État initial : { icuParameters: {}, nabuIdIcuError: null }.
Délègue entièrement le rendu au composant NabuMessage de goblin-nabu, en lui passant entityId et id.
Formulaire d'édition d'une entité locale.
Service Goblin de type workitem. Le bouton "reset" est supprimé de l'interface (via la fonction buttons). Le hinter locale permet de sélectionner une locale existante via autocomplétion et appelle setLocaleId sur validation.
Affiche les trois champs de la locale (name, text, description). Le champ name (nom technique) est en lecture seule si la locale est déjà publiée (status !== 'draft'), pour éviter les incohérences d'identifiant. Le composant est connecté au store via Widget.connect pour lire le statut de l'entité depuis backend.{entityId}.meta.status.
Assistant en deux étapes pour importer un fichier de traductions packagé (.json).
Wizard construit avec buildWizard de goblin-desktop.
- Étape
selectFilePath: formulaire avec unFileInputacceptant les.json. Le bouton "Import" est désactivé tant qu'aucun fichier n'est sélectionné. ModeupdateButtonsMode: 'onChange'pour réévaluation à chaque modification. - Étape
showImportedMessages: exécutenabuAPI.importPackedMessagesavec le chemin sélectionné, puis affiche le nombre de messages et traductions importés. En cas d'erreur, les compteurs tombent à 0.
Étape 1 : FileInput avec filtre .json lié au modèle .form.filePath.
Étape 2 : deux champs Field en mode label affichant importedMessages et importedTranslations.
Assistant en deux étapes pour créer une nouvelle locale.
Wizard construit avec buildWizard. Utilise la bibliothèque langmap pour générer la liste complète des locales standard (format BCP 47 normalisé en _).
- Étape
createLocale: sélection d'une locale dans le combo, saisie optionnelle d'une sub-locale (/valais,/romandie…), nom commun et description. Le bouton "Créer" est désactivé si la sub-locale contient-ou_(caractères non supportés), ou si la locale combinée existe déjà (vérifiée vianabuAPI.findBestLocaleavecavoidCompareLanguages: true). - Étape
localeCreated: appellenabuAPI.tryAddLocalesavec la locale construite (idlocale@{localeName}), puis affiche un message de confirmation.
Le nom de locale final est construit par constructLocaleName(locale, sublocale) : les deux parties sont jointes par /, la sub-locale est normalisée en minuscules et le / initial éventuel est supprimé.
Étape 1 : combo de locales (liste langmap), champ sub-locale, nom commun et description. Un mapper extrait form.locales de l'état pour alimenter le combo.
Étape 2 : label de confirmation de création.
Ces fichiers exposent les commandes sur le bus via exports.xcraftCommands :
| Fichier | Commandes exposées |
|---|---|
nabu-store.js |
Service principal nabu-store (lib/service.js) |
locale.js |
Service CRUD de l'entité locale (entities/locale.js) |
nabuMessage.js |
Service CRUD de l'entité nabuMessage (entities/nabuMessage.js) |
nabuTranslation.js |
Service CRUD de l'entité nabuTranslation (entities/nabuTranslation.js) |
locale-workitem.js |
Workitem d'édition de locale (widgets/locale-workitem/service.js) |
locale-search.js |
Workitem de recherche de locales (goblin-workshop) |
locale-hinter.js |
Hinter Elasticsearch pour les locales (conditionnel à storageAvailable) |
nabuMessage-workitem.js |
Workitem d'édition de message (widgets/nabuMessage-workitem/service.js) |
nabuMessage-datagrid.js |
Datagrid des messages (widgets/nabuMessage-datagrid/service.js) |
nabuMessage-hinter.js |
Hinter Elasticsearch pour les messages (conditionnel à storageAvailable) |
importPackedMessages-wizard.js |
Wizard d'import de pack (widgets/importPackedMessages-wizard/service.js) |
newLocale-wizard.js |
Wizard de création de locale (widgets/newLocale-wizard/service.js) |
Ce module est distribué sous licence MIT.
Ce contenu a été généré par IA