Raccourci clavier : Ctrl + K
Commencer

Localisation (i18n)

Aucun libellé destiné à l'utilisateur n'est codé en dur au sein des composants : l'intégralité des chaînes de caractères est résolue dynamiquement via un dictionnaire de traduction. La bibliothèque est configurée en anglais (en) par défaut et fournit nativement la locale française (fr). L'ajout de langues supplémentaires s'effectue directement au niveau de l'application consommatrice.

La localisation repose sur un découplage strict entre le vocabulaire et le formatage. Les libellés textuels sont issus des dictionnaires de traduction, tandis que le formatage des données (dates, nombres, premier jour de la semaine et cycle 12/24h) s'appuie directement sur l'API native Intl à partir du tag de langue. Ainsi, déclarer une locale sans dictionnaire associé applique immédiatement les conventions régionales appropriées pour les données, tout en conservant les libellés d'interface en anglais. Ce comportement constitue une stratégie de dégradation gracieuse (graceful degradation) parfaitement maîtrisée.

Changer de langue

L'activation d'une locale repose sur deux étapes distinctes : l'enregistrement du dictionnaire de traduction, puis le choix de la locale active. Bien que fourni par la bibliothèque, le dictionnaire français (fr) est optionnel : l'omettre de vos imports suffit à l'exclure du bundle final (tree-shaking). Au-delà de l'optimisation du poids (inférieur à 1 Ko gzippé), ce modèle unifie l'intégration : activer la locale française intégrée ou ajouter une langue sur mesure s'effectue via un mécanisme rigoureusement identique, sans distinction de statut entre dictionnaires natifs et tiers.

ts
import { fr, registerMessages, setLocale } from 'vectis-ui'

registerMessages('fr', fr)
setLocale('fr-FR')

L'enregistrement des dictionnaires et la sélection initiale de la locale s'effectuent au niveau du module (dans main.ts ou un plugin Nuxt), en dehors du hook setup() des composants. Par la suite, setLocale peut être invoqué dynamiquement depuis n'importe quel point de l'application. La table de traduction s'appuyant sur l'état réactif de Vue, sa mise à jour déclenche le re-rendu immédiat de tous les composants montés, sans nécessiter de navigation ni de rechargement de page.

L'état d'i18n étant conservé au niveau du module, la locale est globale pour un processus d'exécution donné. Ce choix d'architecture implique une contrainte explicite : un même processus Node.js ne maintient qu'une seule locale active à la fois. Le rendu côté serveur (SSR) dynamique et concurrentiel par requête n'est donc pas pris en charge nativement ; dans ce scénario, les libellés doivent être transmis explicitement via les props des composants.
En revanche, cette limitation est sans impact sur le pré-rendu statique (SSG), les routes étant générées de façon séquentielle : la locale est définie juste avant la compilation de chaque page, garantissant la génération conforme de l'interface dans la langue ciblée.

Ajouter une langue

Un dictionnaire sur mesure est un simple objet JavaScript. La déclaration de dictionnaires partiels est totalement valide : toute clé absente retombe automatiquement sur le dictionnaire anglais au lieu d'afficher une clé technique brute. Enregistrez l'objet sous son sous-tag de langue, puis définissez la locale active.

ts
import { registerMessages, setLocale, type MessagesInput } from 'vectis-ui'

// Partial is legitimate: what is missing falls back to English.
const de: MessagesInput = {
  common: { clear: 'Leeren', close: 'Schließen' },
  dataTable: { empty: 'Keine Daten' },
}

registerMessages('de', de)
setLocale('de-DE')

En typant l'objet avec MessagesInput, l'éditeur fournit l'autoplétion complète pour les espaces de noms, les clés et les arguments des messages paramétrés. Les entrées textuelles sont formulées sous forme de fonctions TypeScript typées, sans dépendance à un moteur ICU ou de pluralisation complexe : la gestion des pluriels s'effectue par de simples expressions ternaires au sein des fonctions. La fusion des dictionnaires est non récursive par conception, l'arborescence étant strictement limitée à deux niveaux afin de préserver l'intégrité des fonctions de message.

La résolution textuelle s'appuie sur le sous-tag de langue (ex. en-GB et en-US partagent le dictionnaire en et ne diffèrent que par leurs formats Intl). Au sommet de la hiérarchie, les props explicites restent prioritaires : la chaîne de résolution pour le nom accessible d'un composant suit l'ordre de préséance suivant : aria-labelledby -> aria-label -> prop label -> dictionnaire actif -> dictionnaire anglais de secours. L'interface garantit l'absence de chaînes vides, de clés brutes à l'écran ou d'échecs silencieux en mode développement.

Langues et les formats

Le dictionnaire de traduction et la locale de formatage constituent deux réglages strictement indépendants. Tandis que registerMessages et setLocale déterminent la couche lexicale (les chaînes traduites), le code de locale (ainsi que la prop locale disponible sur les composants concernés) pilote les conventions régionales dérivées de l'API Intl (ordonnancement et séparateurs de dates, premier jour de la semaine, format 12/24h). Cette étanchéité permet d'associer librement un dictionnaire linguistique à un code régional distinct : une application peut par exemple afficher ses libellés en français tout en appliquant les formats canadiens anglais (en-CA), ou conserver une interface en anglais avec un formatage adapté à l'Allemagne (de-DE).


Nomenclature et référence des clés de traduction

Le dictionnaire complet de Vectis UI s'articule autour de 134 clés réparties au sein de 22 espaces de noms (namespaces), présentées ci-dessous avec leurs valeurs françaises à titre de référence. L'enregistrement supportant l'injection partielle, vous n'avez besoin de déclarer que les espaces de noms et les clés que vous souhaitez explicitement traduire.

Parmi ces clés, 22 sont des fonctions TypeScript paramétrées. Leur signature expose la liste des arguments attendus ainsi que leur placement dans la chaîne produite. En l'absence de moteur ICU ou de parseur de pluriel dédié, la gestion des formes grammaticales (dont la pluralisation) repose directement sur la logique conditionnelle native (expressions ternaires JS/TS), offrant toute la souplesse nécessaire aux langues complexes.

CléValeur française
common.loadingChargement…
common.clearEffacer
common.closeFermer
common.dismissRetirer
common.remove(name) => `Retirer ${name}`
common.cancelAnnuler
common.confirmOK
pagination.labelPagination
pagination.previousPage précédente
pagination.nextPage suivante
pagination.page(page) => `Page ${page}`
tabs.labelOnglets
tabs.previousOnglets précédents
tabs.nextOnglets suivants
breadcrumb.labelFil d'Ariane
breadcrumb.ellipsisAfficher les pages intermédiaires
sideNavigation.labelNavigation
combobox.emptyAucun résultat
combobox.clearEffacer la sélection
dataTable.emptyAucune donnée
dataTable.loadingChargement des données…
dataTable.searchLabelRechercher dans le tableau
dataTable.searchPlaceholderRechercher…
dataTable.perPageLignes par page
dataTable.perPageValue(label, value) => `${label} : ${value}`
dataTable.selectAllTout sélectionner
dataTable.selectRow(index) => `Sélectionner la ligne ${index}`
dataTable.selection(count) => `${count} élément${count > 1 ? 's' : ''} sélectionné${count > 1 ? 's' : ''}`
dataTable.range({ start, end, total }) => `${start}–${end} sur ${total}`
dataTable.paginationPagination du tableau
toaster.labelNotifications
snackbar.labelConfirmation
snackbar.actionAnnuler
inputOTP.labelCode de vérification
inputOTP.slot(index, total) => `Caractère ${index} sur ${total}`
slider.valueValeur
slider.startDébut
slider.endFin
slider.rangeStart(label) => `${label} (début)`
slider.rangeEnd(label) => `${label} (fin)`
field.limitExceeded(max) => `Dépasse la limite de ${max} caractères`
progress.percent(percent) => `${percent}\u00A0%`
progress.labelProgression
hotkeys.commandCommande
hotkeys.ctrlCtrl
hotkeys.altAlt
hotkeys.shiftMaj
hotkeys.windowsWin
hotkeys.superSuper
hotkeys.enterEntrée
hotkeys.escapeÉchap
hotkeys.spaceEspace
hotkeys.backspaceRetour arrière
hotkeys.deleteSuppr
hotkeys.tabTab
hotkeys.upFlèche haut
hotkeys.downFlèche bas
hotkeys.leftFlèche gauche
hotkeys.rightFlèche droite
hotkeys.label(keys) => `Raccourci clavier : ${keys}`
datePicker.labelSélecteur de date
datePicker.previousMonthMois précédent
datePicker.nextMonthMois suivant
datePicker.previousYearAnnée précédente
datePicker.nextYearAnnée suivante
datePicker.monthPickerChoix du mois
datePicker.yearPickerChoix de l'année
dateInput.clearEffacer la date
dateInput.openPickerOuvrir le calendrier
dateInput.pickerLabelChoisir une date
timePicker.labelSélecteur d'heure
timePicker.meridiemAM ou PM
timePicker.amAM
timePicker.pmPM
timePicker.selectHourSélectionner l’heure
timePicker.selectMinutesSélectionner les minutes
timePicker.choosingHourSélection de l’heure
timePicker.choosingMinutesSélection des minutes
timePicker.hourHeure
timePicker.minutesMinutes
timePicker.hourValue(hour) => `${hour} heures`
timePicker.minutesValue(minute) => `${minute} minutes`
timeInput.clearEffacer l'heure
timeInput.openPickerOuvrir le sélecteur d’heure
timeInput.pickerLabelChoisir une heure
timeInput.meridiemValue(value) => `AM ou PM : ${value}`
timeInput.maskPlaceholderhh:mm
timeInput.unavailableCette heure n'est pas disponible.
fileInput.openPickerChoisir des fichiers
fileInput.clearEffacer les fichiers
fileInput.files(count) => `${count} fichier${count > 1 ? 's' : ''}`
fileInput.placeholderAucun fichier sélectionné
filePicker.browseParcourir les fichiers
filePicker.orou
filePicker.listFichiers sélectionnés
carousel.labelCarrousel
carousel.roleDescriptioncarrousel
carousel.slideRoleDescriptiondiapositive
carousel.slidesDiapositives
carousel.slide(index, total) => `${index} sur ${total}`
carousel.previousDiapositive précédente
carousel.nextDiapositive suivante
carousel.indicatorsChoisir la diapositive à afficher
calendar.labelCalendrier
calendar.roleDescriptioncalendrier
calendar.todayAujourd'hui
calendar.viewAffichage
calendar.viewDayJour
calendar.view4Days4 jours
calendar.viewWeekSemaine
calendar.viewMonthMois
calendar.viewYearAnnée
calendar.viewCustom(days) => `${days} jours`
calendar.previousDayJour précédent
calendar.nextDayJour suivant
calendar.previousWeekSemaine précédente
calendar.nextWeekSemaine suivante
calendar.previousMonthMois précédent
calendar.nextMonthMois suivant
calendar.previousYearAnnée précédente
calendar.nextYearAnnée suivante
calendar.previousPeriodPériode précédente
calendar.nextPeriodPériode suivante
calendar.allDayJournée
calendar.moreEvents(count) => `+${count} autre${count > 1 ? 's' : ''}`
calendar.openDay(day) => `Ouvrir le ${day}`
calendar.untitled(Sans titre)
calendar.eventRoleDescriptionévènement
calendar.eventHintAppuyez sur Entrée pour ouvrir cet évènement. Appuyez sur Espace pour le saisir, puis sur les flèches pour le déplacer et sur Maj avec les flèches pour changer son heure de fin.
calendar.grabbedÉvènement saisi. Utilisez les flèches pour le déplacer, Entrée ou Espace pour le poser, Échap pour annuler.
calendar.droppedÉvènement posé.
calendar.revertedDéplacement annulé. L'évènement est revenu à sa place.
calendar.movedTo(title, when) => `${title} déplacé au ${when}.`