eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsArchitektur
Lokalisierung
Edit this page
11 min read
Mehrsprachigkeit in eQuantic.UI folgt einem Gesetz: Wer entwickelt, lokalisiert mit dem, was .NET selbst anbietet: .resx, dem stark typisierten Accessor, CultureInfo. Es sieht nie einen JavaScript-Katalog oder eine DSL des Frameworks. Der vollständige Entwurf (Track L: wie eqc ResourceManager in den Browser brückt, Kataloge je Kultur, die Formatierungs-Teilmenge, die ehrlichen Zäune) lebt im Repository unter docs/I18N-PLAN.md. Diese Seite dokumentiert, was heute ausgeliefert ist und was noch Entwurf ist.
Ausgeliefert
Die eigenen Strings des SDK leben hinter einer Naht
Seit 0.2.0-preview.24
Komponenten verdrahten nie einen an den Nutzer gerichteten String fest. Jedes eingebaute Label (die Umschaltankündigungen, die ein Screenreader hört ("Checked", "On"), die Verwerfen-Affordanzen, der Suchplatzhalter, die Suchleiste des Code-Editors) liest SdkStrings, eine statische Klasse aus Eigenschaften (nie Konstanten: Eine Konstante überquert Assemblys per WERT, und inlinete Aufrufstellen würden nach dem Lernen der Kulturen weiter die Sprache der Build-Maschine ankündigen). Diese Naht ist heute resx-gestützt: Jede Eigenschaft liest SdkResources, einen gewöhnlichen Designer-Accessor, und keine Komponente hat sich dabei geändert. Im Web schreibt eqc den Accessor in eine Suche im Kulturkatalog um, den die Shell inlinet, sodass dieselbe Eigenschaft auf dem Server aus einem Satellitenassembly und im Browser aus JSON antwortet.
Eine Anwendung mit null eigenen resx ist dadurch schon lokalisiert: Die Schlüssel des SDK treten dem Katalog jeder Kultur bei, die die Anwendung ausgibt, sodass ein Screenreader "Marcado" auf einer pt-BR-Seite hört, die niemand übersetzt hat.
Welche Sprachen ausgeliefert werden, und wie man weniger ausliefert. Die Strings des SDK sind heute nach pt-BR und es übersetzt, und sie reisen IM Paket als Satellitenassemblys (lib/net10.0/<culture>/eQuantic.UI.Components.resources.dll, je etwa 8 KB): ein Paket, das seine Sprachen trägt, wie Roslyn und die Testplattform die ihren ausliefern, statt ein Paket je Sprache. Eine Anwendung, die nur einige davon will, braucht kein anderes Paket, weil .NET diese Entscheidung schon besitzt:
1
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
Gemessen an einem Konsumenten des veröffentlichten Pakets: Ohne sie trägt die Ausgabe der Anwendung es/ und pt-BR/; mit ihr keines von beiden. Die eine Stelle, an der sie nicht gilt, ist eine PROJEKT-Referenz: Die eigenen Beispiele dieses Repositorys behalten die Satelliten in beiden Fällen, weil die Eigenschaft filtert, was über ein Paket ankommt.
Photon-Fenster übernehmen beim Start die Locale der Maschine
Seit 0.2.0-preview.24
Ein aus dem Finder gestarteter GUI-Prozess trägt kein LANG/LC_*, also startet .NET invariant, selbst auf einer pt-BR-Maschine. Die Shells lösen die Locale-Wahrheit der Plattform vor dem ersten Frame auf: NSLocale.preferredLanguages[0] als UI-Kultur und currentLocale als Formatkultur bei Apple (dieselbe Trennung, die .NET als CurrentUICulture gegen CurrentCulture modelliert), Locale.getDefault(), das auf Android beide versorgt, und PhotonCultureController kopiert das Paar auf die Prozess-Statics und malt bei einem späteren Apply genau so neu, wie der Theme-Controller bei einem Moduswechsel neu malt. Weil Photon echtes .NET ohne Transpilation ausführt, funktionieren ResourceManager und Satellitenassemblys dort schon: Eine native Anwendung lokalisiert heute mit gewöhnlichem resx.
Eine Seite spricht resx im Web, Ende zu Ende
Seit 0.2.0-preview.26
Das M0 von Track L: Eine Anwendung schreibt Lokalisierung genau so, wie jede .NET-Anwendung es tut: ein Resources/Strings.resx plus Strings.pt-BR.resx, die gewöhnlichen Designer-Accessoren, string.Format(Strings.Greeting, name), und die Seite antwortet in der Kultur der Anfrage auf BEIDEN Hälften des Web-Ziels.
GET /page + Accept-Language
wählt den Katalog (exakt, Eltern, neutral)
Shell + window.__EQ_CULTURE__
der Boot installiert die Kultur
hydriert mit DENSELBEN Strings
Browser
Server
Runtime
Die Teile, die Form der Theme-Brücke Zug für Zug spiegelnd:
•
eqc schreibt Accessoren um, es inlinet sie nie. Strings.Hero_Title kompiliert zu $eq.str("Strings", "Hero.Title"), denn Inlinen würde die Kultur der Build-Maschine ins Bundle einbacken. Die Erkennung geschieht nach FORM (die statischen ResourceManager- und Culture-Eigenschaften des Designers), also funktioniert jedes resx an jeder Stelle des Projekts, wie auch immer wer entwickelt es benannt hat.
•
Der Build gibt Kataloge aus den Schlüsseln aus, die die Anwendung wirklich nutzt. wwwroot/_equantic/strings/neutral.json plus eine {culture}.json je geschriebener Variante, jede zur Ausgabezeit gegen die Rückfallkette von .NET FLACHGEKLOPFT: Der Server wählt eine Datei, er verschmilzt nie.
•
Der Server inlinet die Antwort. UseRequestLocalization (die ANWENDUNG verdrahtet die Verhandlung, das SDK liest nur, was die Middleware gesetzt hat) treibt <html lang> und einen Shell-Platz window.__EQ_CULTURE__ = { name, formatName, format, strings }; der Boot installiert ihn VOR der Hydration, sodass der Client genau die Strings auflöst, die der Server gerendert hat. format sind die eigenen NumberFormatInfo und DateTimeFormatInfo der Formatkultur, auf jeder Seite aus .NET geschrieben, ob die Anwendung einen String zu übersetzen hat oder nicht: Der Browser schreibt jede Zahl und jedes Datum und benennt jeden Monat eines Kalenders daraus (Seit 0.2.0-preview.61).
•
string.Format über einem Accessor wird beim Build validiert. EQ2100 weist Argumente ab, die keine Strings sind, dazu Formatspezifizierer und Abweichungen in der Stelligkeit gegen das neutrale resx, die M0-Teilmenge von D7 des Plans.
Die Sprache zu wechseln rendert neu, es lädt nie neu
Seit 0.2.0-preview.27
ICultureController ist die Hand der Anwendung an der SPRACHE, in der Form von IThemeController, nach Schnittstellenname aufgelöst, sodass eine Komponente, die einen Umschalter anbietet, nie erfährt, welches Ziel geantwortet hat. Sie spricht BCP-47-Namen, nie CultureInfo: Der Kontrakt überquert in einen Browser, in dem es diesen Typ nicht gibt, und ein Name ist die eine Währung, die .NET, das Web und beide Mobilplattformen teilen.
•
Web: setCulture holt den Katalog der Kultur nur dann, wenn er nicht schon im Speicher ist, und wählt die Datei so, wie der SERVER sie wählt (exakt → Eltern → neutral; die Kette wurde beim Build flachgeklopft, die Suche bleibt also flach), und die Daten der Formatkultur aus /_equantic/culture/{name}.json (Seit 0.2.0-preview.61), tauscht sie und rendert über denselben Scheduler neu, den SetState nutzt. Der Zustand überlebt; nur geänderter Text wird angefasst. Der Wechsel überdauert auch die Seite: Der Controller schreibt ASP.NETs eigenen Kultur-Cookie, in ASP.NETs eigenem Format, sodass die nächste Anfrage schon übersetzt ankommt.
•
Native: PhotonCultureController realisiert dieselbe Schnittstelle: Die Prozess-Statics ändern sich und das Fenster malt neu.
•
CultureSwitcher ist die eingebaute Fläche: ein Segmented Control für zwei oder drei Sprachen, darüber hinaus ein Menü, das die aktive Option nach exaktem Namen und dann nach dem Sprachteil hervorhebt (eine als pt-BR ausgelieferte Seite muss einen pt-Eintrag hervorheben).
1
2
3
4
5
new CultureSwitcher([
new("en", "English"),
new("pt-BR", "Português"),
new("es", "Español"),
])
Ein Formatspezifizierer übersteht die Reise
Seit 0.2.0-preview.27
{0:C2}, einmal in C# geschrieben, druckt R$ 1.234,50 auf einer pt-BR-Seite und 1234,50 € auf einer es-Seite, wobei SSR und Client Zeichen für Zeichen übereinstimmen, und ein Kulturwechsel formatiert live mit den Strings neu. Die D7-Teilmenge: N/F/P/C/D/E mit Präzision, die Standardbuchstaben für Datum und Zeit, eigene Ziffern- und Datumsschablonen und das nackte {0} (das in .NET NICHT invariant ist, und nun auch im Browser nicht mehr).
Der Beweis ist die Methode: Die gepinnte Fixture wird aus echtem .NET generiert (value.ToString(spec, culture), sieben Kulturen, gewählt, um sich zu widersprechen) und die transpilierte Runtime muss sie Byte für Byte reproduzieren, ein geschütztes Leerzeichen eingeschlossen. Diese Richtung hat jede Abweichung zur Build-Zeit gefangen. Intl und .NET lesen verschiedene ICU-Builds und legen eine Zahl nach verschiedenen Tabellen aus: Intl schrieb die eigenen Ziffern von ar-EG, wo .NET ASCII-Ziffern schreibt, ein geschütztes Leerzeichen, wo das Währungsmuster von .NET ein normales hat, und zwei Stellen für N, wo .NET auf ICU drei liest. Also zeichnet der Browser jede Zahl und jedes Datum aus den eigenen NumberFormatInfo und DateTimeFormatInfo der Kultur, die der Server mit der Seite sendet, und Intl legt nur eine Kultur aus, zu der ohne Server zum Fragen gewechselt wurde (Seit 0.2.0-preview.61).
Was sich nicht einigen kann, wird beim Build abgelehnt, nie angenähert: EQ2100 weist Ausrichtung und Spezifizierer außerhalb der Teilmenge in einer resx-Schablone ab; EQ2101 hält die Platzhalter jeder Übersetzung gegen das neutrale resx, sodass ein pt-BR-String, der {2} verlangt, wo neutral {0}/{1} hat, den Build scheitern lässt, statt nur für brasilianische Leserinnen zu werfen.
Der Umfang, klar gesagt: Das Versprechen exakter Übereinstimmung für STRINGS gilt für die Kulturen, die die Anwendung ausliefert, und eine zur Laufzeit verhandelte Kultur ohne geschriebenen Katalog fällt auf die neutralen Strings zurück. Ihre FORMATE sind die der Kultur der Anfrage, welche Kataloge auch existieren, aus den Daten geschrieben, die der Server sendet, deterministisch und nie browserabhängig; eine Seite ohne installierte Kultur formatiert in der invarianten Kultur (Seit 0.2.0-preview.61).
Zwei Kulturen in einem Fenster
Seit 0.2.0-preview.29
Die Web-Hälfte dieser Geschichte ist eine Anfrage: eine Kultur hinein, SSR und Hydration stimmen überein. Ein Fenster hat keine Anfrage, ein Wechsel dort hat also drei Gelenke, die das Web nie hat: Die Plattform entscheidet die Anfangs-Locale, jemand kopiert sie auf die Statics von .NET, und der Baum muss NEU BAUEN, damit die Änderung die Pixel erreicht.
Eine Komponente sagt nichts davon. Der Abschnitt Language des Studio-Beispiels ist ein gewöhnlicher CultureSwitcher, der ICultureController aus dem Kontext auflöst und keine Plattform nennt... die gleiche Klasse, die der Browser rendert. Eine Kultur anzuwenden schreibt beide Statics (das Paar aus D13: Ressourcen und Formate sind getrennte Entscheidungen) und malt das Fenster neu, der Wechsel landet also im nächsten Frame, mit jedem Bit Seitenzustand unversehrt.
Der Screenshot-Pfad rendert es auf beide Weisen, und das hat den Meilenstein geschlossen: ohne Argument zeigt das Fenster das Paar der MASCHINE (eine englische Oberfläche mit portugiesischen Formaten, auf einem so eingestellten Mac), und mit --culture pt-BR zeigt es Marcado, Ativado, Planilha neben R$ 1.234,50.
Das Beispiel zu schreiben fand den Fehler, den die Tests nicht finden konnten: Der Host registrierte nur den konkreten Controller, also löste eine Komponente, die die SCHNITTSTELLE verlangte, null auf, und der Umschalter schaltete nichts um. Zwei Registrierungen, eine Instanz. Eine Fähigkeit, die niemand in einem echten Fenster auflöst, ist eine Fähigkeit, die nicht funktioniert.
Eine neue Anwendung wird lokalisiert geboren
Seit 0.2.0-preview.28
dotnet new equantic-app erzeugt nun, was diese Seite predigt: ein Resources/Strings.resx mit seinem pt-BR-Satelliten und dem gewöhnlichen Designer-Accessor, einen CultureSwitcher auf der Startseite und die ganze Geschichte in einer Zeile, string.Format(Strings.CountedTimes, _count). Das erste Projekt einer Entwicklerin antwortet in zwei Sprachen, bevor sie Code geschrieben hat, und das ist der günstigste Moment zu lernen, dass UI-Strings nie festverdrahtet werden. Siehe Erste Schritte für das vollständige Scaffold.
Einem Crawler wird gesagt, welche URL welche Sprache ist
Seit 0.2.0-preview.31
Eine Website zu übersetzen und die Übersetzungsgruppe nie zu deklarieren, so rankt eine lokalisierte Website stillschweigend gegen sich selbst. Eine anwendungsweite Richtlinie, options.UseAlternateLinks(AlternateUrls.PathPrefix(), "en", "pt-BR", "es"), setzt den ganzen rel="alternate" hreflang-Satz plus x-default in den Head jeder Seite, mit erzwungenen Regeln für Gegenseitigkeit und absolute URLs, statt sie der Anwendung zu überlassen. Siehe Server-Integration.
Die Sprache reitet auf der URL
Seit 0.2.0-preview.36
Eine öffentliche Website will jede Übersetzung an ihrer eigenen Adresse (/pt-BR/pricing), weil das die einzige Form ist, die ein Crawler als Übersetzung indexiert und die eine Leserin teilen kann. Eine Deklaration tut es:
1
2
3
4
5
6
builder.Services.AddUI(options => options
.ScanAssembly(typeof(Program).Assembly)
.UseCultureRoutes("en", "pt-BR", "es")); // die erste wird ohne Präfix ausgeliefert
…
app.UseRequestLocalization(); // ASP.NETs eigene Middleware, schon konfiguriert
app.MapUI();
Nichts darin ist eigene Maschinerie des SDK. UseCultureRoutes füllt die RequestLocalizationOptions der Plattform aus dieser Liste (Standardkultur, unterstützte Kulturen und der RouteDataRequestCultureProvider von ASP.NET zuerst gesetzt) und registriert eine culture-Routen-Einschränkung, sodass jede Seite (eine [Page] oder ein MapPage<T>) auch unter /{culture:culture}/… gemappt wird. Von dort:
•
/pt-BR/pricing rendert in pt-BR, und das Segment gewinnt über den Cookie und Accept-Language: Eine URL, die eine Sprache nennt, ist ein Versprechen darüber, was die Seite sagt.
•
Jede anwendungsinterne href trägt die Sprache: Die Autorin schreibt /about einmal, und Server und Browser geben beide /pt-BR/about aus, während die Leserin auf Portugiesisch liest. Ein Link, ein TextRun, der verlinkt, und ein Markdown-Link ebenso (ein Run und ein Markdown-Link *seit 0.2.0-preview.60*: Sie behielten das Ziel wie geschrieben), und jeder interne wird beim Hover vorgewärmt. Absolute URLs, protokollrelative (//cdn…), Anker und mailto: werden in Ruhe gelassen.
•
/fr/pricing ist ein 404, nicht Englisch unter einer französischen Adresse: Die Einschränkung lässt genau die Präfixe zu, die Sie genannt haben.
•
Ein nacktes /pricing, auf Portugiesisch verlangt (der Cookie, den CultureSwitcher schreibt, oder Accept-Language), wird (302) nach /pt-BR/pricing umgeleitet, sodass das, was gesehen, geteilt und als Lesezeichen gesetzt wird, immer seine Sprache nennt. Die Standardkultur behält ihre nackten URLs.
•
Die Sprache zu wechseln ist eine Navigation: CultureSwitcher geht zur gleichen Seite an ihrer anderen Adresse, über den Router, ohne Neuladen.
•
hreflang und x-default kommen aus derselben Karte ohne zweite Deklaration, und ein canonical, das eine Seite von Hand geschrieben hat, wird auf dem Weg hinaus lokalisiert, sodass es einen Crawler nie auf die falsche Übersetzung zeigt.
Eine Anwendung, die die Middleware mit der Lambda-Überladung konfiguriert (app.UseRequestLocalization(o => …)), baut ihre Optionen inline und sieht nichts aus der DI, geben Sie ihr dort also dieselbe Liste mit o.UseCultureRoutes("en", "pt-BR", "es").
Noch Entwurf (Track L, docs/I18N-PLAN.md)
Satelliten unter NativeAOT sind keine Frage mehr: Ein ausführbares Gestell (scripts/verify-aot-satellites.sh) veröffentlicht ein natives Binary gegen die Komponentenbibliothek und weigert sich zu bestehen, solange nicht en, pt-BR und es alle mit der ÜBERSETZUNG antworten, denn ein verlorener Satellit stürzt nicht ab, er antwortet stillschweigend auf Englisch, also vergleicht die Prüfung gegen die erwarteten Übersetzungen.
Noch vor uns: das Aufteilen der Kataloge je Seite, Pluralformen jenseits von zwei und ein IHandleMetadata, das bei einem Kulturwechsel erneut läuft. RTL und die Schriftabdeckung je Schriftsystem sind ausdrücklich andere Spuren.