Repository navigation
Localization fr
🌐 Cette page en : English · Português · Español · Français · Deutsch
Le multilingue dans eQuantic.UI suit une seule loi : qui développe localise avec ce que .NET
lui-même offre : .resx, l'accesseur fortement typé, CultureInfo. Il ne voit jamais un
catalogue JavaScript ni un DSL du framework. La conception complète (Track L : comment eqc ponte
ResourceManager vers le navigateur, les catalogues par culture, le sous-ensemble de formatage, les
limites honnêtes) vit dans le dépôt, dans docs/I18N-PLAN.md. Cette page documente ce qui est
livré aujourd'hui et ce qui reste de la conception.
Depuis 0.2.0-preview.24
Les composants ne codent jamais en dur une chaîne destinée à l'utilisateur. Chaque libellé intégré
(les annonces de bascule qu'entend un lecteur d'écran ("Checked", "On"), les affordances de
fermeture, le placeholder de recherche, la barre de recherche de l'éditeur de code) lit
SdkStrings, une classe statique de propriétés (jamais de constantes : une constante traverse les
assemblys par VALEUR, et des sites d'appel inlinés continueraient d'annoncer la langue de la machine
de build après que la couture a appris les cultures). Cette couture est adossée au resx aujourd'hui :
chaque propriété lit SdkResources, un accesseur Designer ordinaire, et aucun composant n'a changé
quand c'est arrivé. Sur le web, eqc réécrit l'accesseur en une recherche dans le catalogue de
culture que le shell inline, si bien que la même propriété répond depuis un assembly satellite sur le
serveur et depuis du JSON dans le navigateur.
Une application avec zéro resx à elle est déjà localisée par cela : les clés du SDK rejoignent le catalogue de chaque culture que l'application émet, donc un lecteur d'écran entend "Marcado" sur une page pt-BR que personne n'a traduite.
Quelles langues sont livrées, et comment en livrer moins. Les chaînes du SDK sont traduites en
pt-BR et en es aujourd'hui, et elles voyagent DANS le paquet sous forme d'assemblys satellites
(lib/net10.0/<culture>/eQuantic.UI.Components.resources.dll, environ 8 Ko chacun) : un paquet qui
porte ses langues, comme Roslyn et la plateforme de test livrent les leurs, plutôt qu'un paquet par
langue. Une application qui n'en veut que certaines n'a pas besoin d'un paquet différent, parce que
.NET possède déjà cette décision :
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>Mesuré sur un consommateur du paquet publié : sans elle, la sortie de l'application porte es/ et
pt-BR/ ; avec elle, aucun des deux. Le seul endroit où cela ne s'applique pas est une référence de
PROJET : les exemples de ce dépôt gardent les satellites dans les deux cas, parce que la propriété
filtre ce qui arrive par un paquet.
Depuis 0.2.0-preview.24
Un processus graphique lancé depuis le Finder ne porte pas LANG/LC_*, donc .NET démarre invariant
même sur une machine pt-BR. Les shells résolvent la vérité de la locale de la plateforme avant la
première frame : NSLocale.preferredLanguages[0] comme culture d'interface et currentLocale comme
culture de format chez Apple (la même séparation que .NET modélise entre CurrentUICulture et
CurrentCulture), Locale.getDefault() alimentant les deux sur Android, et
PhotonCultureController copie la paire sur les statiques du processus, en repeignant lors d'un
Apply ultérieur exactement comme le contrôleur de thème repeint lors d'un changement de mode.
Comme Photon exécute du vrai .NET sans transpilation, ResourceManager et les assemblys satellites y
fonctionnent déjà : une application native se localise aujourd'hui avec du resx ordinaire.
Depuis 0.2.0-preview.26
Le M0 du Track L : une application écrit la localisation exactement comme le fait toute application
.NET : un Resources/Strings.resx plus Strings.pt-BR.resx, les accesseurs Designer ordinaires,
string.Format(Strings.Greeting, name), et la page répond dans la culture de la requête sur LES DEUX
moitiés de la cible web.
sequenceDiagram
participant B as Navigateur
participant S as Serveur
participant R as Runtime
B->>S: GET /page + Accept-Language
S->>S: choisit le catalogue (exact, parent, neutre)
S-->>B: shell + window.__EQ_CULTURE__
B->>R: le boot installe la culture
R->>R: hydrate avec les MÊMES chaînes
Les pièces, reflétant la forme du pont de thème trait pour trait :
-
eqcréécrit les accesseurs, il ne les inline jamais.Strings.Hero_Titlecompile en$eq.str("Strings", "Hero.Title"), parce qu'inliner cuirait la culture de la machine de build dans le bundle. La détection se fait par FORME (les propriétés statiquesResourceManager+Culturedu Designer), donc n'importe quel resx n'importe où dans le projet fonctionne, quel que soit le nom que lui a donné qui développe. -
Le build émet des catalogues à partir des clés que l'application utilise vraiment.
wwwroot/_equantic/strings/neutral.jsonplus un{culture}.jsonpar variante écrite, chacun APLATI contre la chaîne de repli de .NET au moment de l'émission : le serveur choisit un fichier, il ne fusionne jamais. -
Le serveur inline la réponse.
UseRequestLocalization(l'APPLICATION câble la négociation, le SDK ne lit que ce que le middleware a posé) conduit<html lang>et un emplacement de shellwindow.__EQ_CULTURE__ = { name, formatName, format, strings }; le boot l'installe AVANT l'hydratation, donc le client résout exactement les chaînes que le serveur a rendues.formatest leNumberFormatInfoet leDateTimeFormatInfopropres à la culture de format, écrits depuis .NET sur chaque page, que l'application ait ou non une chaîne à traduire : le navigateur écrit chaque nombre et chaque date, et nomme chaque mois d'un calendrier, à partir de cela (Depuis 0.2.0-preview.61). -
string.Formatsur un accesseur est validé au build. EQ2100 rejette les arguments qui ne sont pas des chaînes, les spécificateurs de format et les écarts d'arité face au resx neutre, le sous-ensemble M0 du D7 du plan.
Depuis 0.2.0-preview.27
ICultureController est la main de l'application sur la LANGUE, dans la forme d'IThemeController,
résolu par nom d'interface, si bien qu'un composant qui offre un sélecteur n'apprend jamais quelle
cible a répondu. Il parle des noms BCP-47, jamais CultureInfo : le contrat traverse vers un
navigateur où ce type n'existe pas, et un nom est la seule monnaie que .NET, le web et les deux
plateformes mobiles partagent.
-
Web :
setCulturetélécharge le catalogue de la culture seulement quand il n'est pas déjà en mémoire, en choisissant le fichier comme le SERVEUR le choisit (exact → parents → neutre ; la chaîne a été aplatie au build, la recherche reste donc plate), et les données de la culture de format depuis/_equantic/culture/{name}.json(Depuis 0.2.0-preview.61), les échange, et re-rend par le même ordonnanceur qu'utiliseSetState. L'état survit ; seul le texte qui a changé est touché. Le changement survit aussi à la page : le contrôleur écrit le cookie de culture propre à ASP.NET, dans le format propre à ASP.NET, donc la requête suivante arrive déjà traduite. -
Natif :
PhotonCultureControllerréalise la même interface : les statiques du processus changent et la fenêtre se repeint. -
CultureSwitcherest la surface intégrée : un contrôle segmenté pour deux ou trois langues, un menu au-delà, qui allume l'option active par nom exact puis par la part de langue (une page servie enpt-BRdoit allumer une entréept).
new CultureSwitcher([
new("en", "English"),
new("pt-BR", "Português"),
new("es", "Español"),
])Depuis 0.2.0-preview.27
{0:C2} écrit une fois en C# imprime R$ 1.234,50 sur une page pt-BR et 1234,50 € sur une page
es, le SSR et le client s'accordant caractère pour caractère, et un changement de culture reformate en
direct avec les chaînes. Le sous-ensemble D7 : N/F/P/C/D/E avec précision, les lettres
standard de date et d'heure, les gabarits personnalisés de chiffres et de date, et le {0} nu (qui
n'est PAS invariant en .NET, et qui ne l'est plus dans le navigateur non plus).
La preuve est la méthode : la fixture épinglée est générée depuis du vrai .NET
(value.ToString(spec, culture), sept cultures choisies pour se contredire) et le runtime transpilé
doit la reproduire octet pour octet, une espace insécable comprise. Cette direction a attrapé chaque
divergence au build. Intl et .NET lisent des builds d'ICU différents et disposent un nombre selon
des tables différentes : Intl écrivait les chiffres propres à ar-EG là où .NET écrit les ASCII, une
espace insécable là où le motif monétaire de .NET en a une normale, et deux chiffres pour N là où
.NET en lit trois sur ICU. Donc le navigateur dessine chaque nombre et chaque date depuis les
NumberFormatInfo et DateTimeFormatInfo propres à la culture, que le serveur envoie avec la page,
et Intl ne dispose qu'une culture vers laquelle on a basculé sans serveur à interroger
(Depuis 0.2.0-preview.61).
Ce qui ne peut pas s'accorder est refusé au build, jamais approché : EQ2100 rejette l'alignement
et les spécificateurs hors sous-ensemble dans un gabarit resx ; EQ2101 tient les placeholders de
chaque traduction face au resx neutre, donc une chaîne pt-BR qui demande {2} là où le neutre a
{0}/{1} fait échouer le build au lieu de lever pour les lectrices brésiliennes seules.
La portée, dite simplement : la promesse d'accord exact pour les CHAÎNES couvre les cultures que l'application livre, et une culture négociée à l'exécution sans catalogue écrit dégrade vers les chaînes neutres. Ses FORMATS sont ceux de la culture de la requête, quels que soient les catalogues existants, écrits depuis les données que le serveur envoie, déterministes et jamais dépendants du navigateur ; une page sans culture installée formate dans la culture invariante (Depuis 0.2.0-preview.61).
Depuis 0.2.0-preview.29
La moitié web de cette histoire est une requête : une culture entre, le SSR et l'hydratation s'accordent. Une fenêtre n'a pas de requête, donc un changement là-bas a trois articulations que le web n'a jamais : la plateforme décide de la locale initiale, quelqu'un la copie sur les statiques de .NET, et l'arbre doit se RECONSTRUIRE pour que le changement atteigne les pixels.
Un composant ne dit rien de tout cela. La section Language de l'exemple Studio est un
CultureSwitcher ordinaire, qui résout ICultureController depuis le contexte et ne nomme aucune
plateforme... la même classe que rend le navigateur. Appliquer une culture écrit les deux statiques
(la paire du D13 : les ressources et les formats sont des décisions séparées) et repeint la fenêtre,
donc le changement atterrit à la frame suivante avec chaque bit d'état de page intact.
Le chemin de capture d'écran le rend des deux façons, et c'est ce qui a clos le jalon : sans argument
la fenêtre montre la paire de la MACHINE (une interface en anglais avec des formats portugais, sur un
Mac réglé ainsi), et avec --culture pt-BR elle montre Marcado, Ativado, Planilha à côté de
R$ 1.234,50.
Écrire l'exemple a trouvé le bug que les tests ne pouvaient pas : l'hôte n'enregistrait que le contrôleur concret, donc un composant demandant l'INTERFACE résolvait null et le sélecteur ne changeait rien. Deux enregistrements, une instance. Une capacité que personne ne résout dans une vraie fenêtre est une capacité qui ne fonctionne pas.
Depuis 0.2.0-preview.28
dotnet new equantic-app génère maintenant ce que cette page prêche : un Resources/Strings.resx
avec son satellite pt-BR et l'accesseur Designer ordinaire, un CultureSwitcher sur la page
d'accueil, et toute l'histoire en une ligne, string.Format(Strings.CountedTimes, _count). Le
premier projet de qui développe répond en deux langues avant d'avoir écrit du code, et c'est le
moment le moins cher pour apprendre que les chaînes d'interface ne se codent jamais en dur. Voir
Premiers pas pour le scaffold complet.
Depuis 0.2.0-preview.31
Traduire un site et ne jamais déclarer le groupe de traduction, c'est ainsi qu'un site localisé se
classe en silence contre lui-même. Une seule politique pour toute l'application,
options.UseAlternateLinks(AlternateUrls.PathPrefix(), "en", "pt-BR", "es"), met tout l'ensemble
rel="alternate" hreflang plus x-default dans le head de chaque page, avec les règles de
réciprocité et d'URL absolue appliquées plutôt que laissées à l'application. Voir
Intégration serveur.
Depuis 0.2.0-preview.36
Un site public veut chaque traduction à sa propre adresse (/pt-BR/pricing) parce que c'est la seule
forme qu'un crawler indexe comme une traduction et qu'un lecteur peut partager. Une déclaration le
fait :
builder.Services.AddUI(options => options
.ScanAssembly(typeof(Program).Assembly)
.UseCultureRoutes("en", "pt-BR", "es")); // la première est servie sans préfixe
…
app.UseRequestLocalization(); // le middleware propre à ASP.NET, déjà configuré
app.MapUI();Rien là-dedans n'est de la machinerie propre au SDK. UseCultureRoutes remplit les
RequestLocalizationOptions de la plateforme depuis cette liste (culture par défaut, cultures prises
en charge, et le RouteDataRequestCultureProvider d'ASP.NET placé en premier), et enregistre une
contrainte de route culture, si bien que chaque page (une [Page] ou un MapPage<T>) est aussi
mappée à /{culture:culture}/…. À partir de là :
-
/pt-BR/pricingse rend en pt-BR, et le segment bat le cookie etAccept-Language: une URL qui nomme une langue est une promesse sur ce que dit la page. -
Chaque href interne porte la langue : l'auteur écrit
/aboutune fois et le serveur comme le navigateur émettent/pt-BR/aboutpendant que le lecteur est en portugais. UnLink, unTextRunqui lie et un lien Markdown de même (un run et un lien Markdown depuis la 0.2.0-preview.60 : ils gardaient la destination telle qu'écrite), et chacun des internes est préchauffé au survol. Les URL absolues, celles relatives au protocole (//cdn…), les ancres etmailto:sont laissées tranquilles. -
/fr/pricingest un 404, pas de l'anglais sous une adresse française : la contrainte admet exactement les préfixes que vous avez nommés. -
Un
/pricingnu demandé en portugais (le cookie qu'écritCultureSwitcher, ouAccept-Language) est redirigé (302) vers/pt-BR/pricing, donc ce qui est vu, partagé et mis en favori nomme toujours sa langue. La culture par défaut garde ses URL nues. -
Changer de langue est une navigation :
CultureSwitcherva à la même page à son autre adresse, par le routeur, sans rechargement. -
hreflangetx-defaultviennent de la même carte sans seconde déclaration, et uncanonicalqu'une page a écrit à la main est localisé en sortie, donc il ne pointe jamais un crawler vers la mauvaise traduction.
Une application qui configure le middleware avec la surcharge lambda
(app.UseRequestLocalization(o => …)) construit ses options en ligne et ne voit rien de la DI,
donnez-lui donc la même liste là avec o.UseCultureRoutes("en", "pt-BR", "es").
Les satellites sous NativeAOT ne sont plus une question : un harnais exécutable
(scripts/verify-aot-satellites.sh) publie un binaire natif contre la bibliothèque de composants et
refuse de passer à moins que en, pt-BR et es répondent tous avec la TRADUCTION, parce qu'un satellite
perdu ne plante pas, il répond silencieusement en anglais, donc la vérification compare aux
traductions attendues.
Encore devant nous : la découpe des catalogues par page, les formes plurielles au-delà de deux, et
IHandleMetadata qui se rejoue lors d'un changement de culture. Le RTL et la couverture de police
par écriture sont explicitement d'autres voies.
🏁 Commencer ici
🏗️ Architecture
- Vue d'ensemble de l'architecture
- Composants write-once
- Surface déclarative
- Architecture des paquets
- Composants
- Styles
- Localisation
- Analytics et GTM
📱 Write-once
- Moteur Photon
- Design System
- Capacités
- Stockage
- Formulaires
- Éditeur de code
- Markdown
- Mermaid
- Rendu des e-mails
⚙️ Compilation
- Compilateur
- Fonctionnalités C# prises en charge
- Résolution des types externes
- Flux de build
- Diagnostics
⚡ Runtime
🔌 Serveur
🎨 Écosystème
- Image · Graphiques · Icônes · Lottie
🚀 Développement