Repository navigation
Localization es
🌐 Esta página en: English · Português · Español · Français · Deutsch
El multi-idioma en eQuantic.UI sigue una sola ley: quien programa localiza con lo que .NET mismo
ofrece: .resx, el accesor fuertemente tipado, CultureInfo. Nunca ve un catálogo de JavaScript
ni un DSL del framework. El diseño completo (Track L: cómo eqc puentea ResourceManager hacia el
navegador, los catálogos por cultura, el subconjunto de formato, las vallas honestas) vive en el
repositorio en docs/I18N-PLAN.md. Esta página documenta lo que está entregado hoy y lo que
sigue siendo diseño.
Desde 0.2.0-preview.24
Los componentes nunca escriben a mano una cadena de cara al usuario. Cada etiqueta incorporada (los
anuncios de alternancia que oye un lector de pantalla ("Checked", "On"), los recursos para
descartar, el placeholder de búsqueda, la barra de buscar del editor de código) lee SdkStrings, una
clase estática de propiedades (nunca constantes: una constante cruza ensamblados por VALOR, y los
sitios de llamada inlineados seguirían anunciando el idioma de la máquina de build después de que la
costura aprenda culturas). Esa costura está respaldada por resx hoy: cada propiedad lee
SdkResources, un accesor Designer corriente, y ningún componente cambió cuando lo hizo. En la web
eqc reescribe el accesor como una búsqueda en el catálogo de cultura que el shell inlinea, así que
la misma propiedad responde desde un ensamblado satélite en el servidor y desde JSON en el navegador.
Una aplicación con cero resx propios ya está localizada por ello: las claves del SDK se suman al catálogo de cada cultura que la aplicación emite, así que un lector de pantalla oye "Marcado" en una página pt-BR que nadie tradujo.
Qué idiomas se entregan, y cómo entregar menos. Las cadenas del SDK están traducidas a pt-BR y
es hoy, y viajan DENTRO del paquete como ensamblados satélite
(lib/net10.0/<culture>/eQuantic.UI.Components.resources.dll, unos 8 KB cada uno): un paquete que
lleva sus idiomas, como entregan los suyos Roslyn y la plataforma de pruebas, en lugar de un paquete
por idioma. Una aplicación que solo quiere algunos de ellos no necesita un paquete distinto, porque
.NET ya es dueño de esa decisión:
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>Medido en un consumidor del paquete publicado: sin él la salida de la aplicación lleva es/ y
pt-BR/; con él, ninguno. El único sitio donde no se aplica es una referencia de PROYECTO: los
ejemplos de este repositorio conservan los satélites de cualquier forma, porque la propiedad filtra
lo que llega a través de un paquete.
Desde 0.2.0-preview.24
Un proceso de GUI lanzado desde Finder no lleva LANG/LC_*, así que .NET arranca invariante incluso
en una máquina pt-BR. Los shells resuelven la verdad del locale de la plataforma antes del primer
frame: NSLocale.preferredLanguages[0] como cultura de interfaz y currentLocale como cultura de
formato en Apple (la misma división que .NET modela como CurrentUICulture frente a
CurrentCulture), Locale.getDefault() alimentando las dos en Android, y
PhotonCultureController copia el par en las estáticas del proceso, repintando en un Apply
posterior exactamente como el controlador de tema repinta al cambiar de modo. Como Photon corre .NET
de verdad sin transpilación, ResourceManager y los ensamblados satélite ya funcionan ahí: una
aplicación nativa se localiza hoy con resx corriente.
Desde 0.2.0-preview.26
El M0 del Track L: una aplicación escribe la localización exactamente como lo hace cualquier
aplicación .NET: un Resources/Strings.resx más Strings.pt-BR.resx, los accesores Designer
corrientes, string.Format(Strings.Greeting, name), y la página responde en la cultura de la
petición en LAS DOS mitades del destino web.
sequenceDiagram
participant B as Navegador
participant S as Servidor
participant R as Runtime
B->>S: GET /page + Accept-Language
S->>S: elige el catálogo (exacto, padre, neutro)
S-->>B: shell + window.__EQ_CULTURE__
B->>R: el boot instala la cultura
R->>R: hidrata con las MISMAS cadenas
Las piezas, reflejando la forma del puente de tema punto por punto:
-
eqcreescribe los accesores, nunca los inlinea.Strings.Hero_Titlecompila a$eq.str("Strings", "Hero.Title"), porque inlinear hornearía la cultura de la máquina de build dentro del bundle. La detección es por FORMA (las propiedades estáticasResourceManager+Culturedel Designer), así que cualquier resx en cualquier sitio del proyecto funciona, como sea que lo haya llamado quien programa. -
El build emite catálogos a partir de las claves que la aplicación usa de verdad.
wwwroot/_equantic/strings/neutral.jsonmás un{culture}.jsonpor variante escrita, cada uno APLANADO contra la cadena de fallback de .NET en el momento de emitir: el servidor elige un archivo, nunca fusiona. -
El servidor inlinea la respuesta.
UseRequestLocalization(la APLICACIÓN conecta la negociación, el SDK solo lee lo que el middleware fijó) conduce<html lang>y una ranura del shellwindow.__EQ_CULTURE__ = { name, formatName, format, strings }; el boot la instala ANTES de la hidratación, así que el cliente resuelve exactamente las cadenas que el servidor renderizó.formatson elNumberFormatInfoy elDateTimeFormatInfopropios de la cultura de formato, escritos desde .NET en cada página, tenga o no la aplicación una cadena que traducir: el navegador escribe cada número y cada fecha, y nombra cada mes de un calendario, a partir de ello (Desde 0.2.0-preview.61). -
string.Formatsobre un accesor se valida en el build. EQ2100 rechaza argumentos que no son cadenas, especificadores de formato y desvíos de aridad contra el resx neutro, el subconjunto M0 del D7 del plan.
Desde 0.2.0-preview.27
ICultureController es la mano de la aplicación sobre el IDIOMA, con la forma de
IThemeController, resuelto por nombre de interfaz, así que un componente que ofrece un switcher
nunca llega a saber qué destino respondió. Habla nombres BCP-47, nunca CultureInfo: el contrato
cruza a un navegador donde ese tipo no existe, y un nombre es la única moneda que comparten .NET, la
web y las dos plataformas móviles.
-
Web:
setCulturedescarga el catálogo de la cultura solo cuando no está ya en memoria, eligiendo el archivo como lo elige el SERVIDOR (exacto → padres → neutro; la cadena se aplanó en el build, así que la búsqueda se queda plana), y los datos de la cultura de formato desde/_equantic/culture/{name}.json(Desde 0.2.0-preview.61), los cambia, y re-renderiza por el mismo planificador que usaSetState. El estado sobrevive; solo se toca el texto que cambió. El cambio además sobrevive a la página: el controlador escribe la propia cookie de cultura de ASP.NET, en el formato propio de ASP.NET, así que la siguiente petición llega ya traducida. -
Nativo:
PhotonCultureControllerrealiza la misma interfaz: las estáticas del proceso cambian y la ventana se repinta. -
CultureSwitcheres la superficie incorporada: un control segmentado para dos o tres idiomas, un menú más allá de eso, encendiendo la opción activa por nombre exacto y luego por la parte de idioma (una página servida comopt-BRdebe encender una entradapt).
new CultureSwitcher([
new("en", "English"),
new("pt-BR", "Português"),
new("es", "Español"),
])Desde 0.2.0-preview.27
{0:C2} escrito una vez en C# imprime R$ 1.234,50 en una página pt-BR y 1234,50 € en una es,
con el SSR y el cliente coincidiendo carácter por carácter, y un cambio de cultura re-formatea en
vivo junto con las cadenas. El subconjunto D7: N/F/P/C/D/E con precisión, las letras
estándar de fecha y hora, las plantillas personalizadas de dígitos y de fecha, y el {0} pelado (que
NO es invariante en .NET, y ahora tampoco lo es en el navegador).
La prueba es el método: el fixture fijado se genera desde .NET real
(value.ToString(spec, culture), siete culturas elegidas para discrepar) y el runtime transpilado
debe reproducirlo byte por byte, un espacio duro incluido. Esa dirección atrapó cada divergencia en
tiempo de build. Intl y .NET leen builds distintos de ICU y disponen un número por tablas
distintas: Intl escribía los dígitos propios de ar-EG donde .NET escribe los ASCII, un espacio duro
donde el patrón de moneda de .NET tiene uno normal, y dos dígitos para N donde .NET lee tres en
ICU. Así que el navegador dibuja cada número y cada fecha desde el NumberFormatInfo y el
DateTimeFormatInfo propios de la cultura, que el servidor envía con la página, e Intl solo
dispone una cultura a la que se cambió sin un servidor al que preguntar
(Desde 0.2.0-preview.61).
Lo que no puede coincidir se rechaza en el build, nunca se aproxima: EQ2100 rechaza la alineación
y los especificadores fuera del subconjunto en una plantilla resx; EQ2101 sostiene los
placeholders de cada traducción contra el resx neutro, así que una cadena pt-BR que pide {2} donde
el neutro tiene {0}/{1} falla el build en lugar de lanzar solo para quien lee en Brasil.
El alcance, dicho con claridad: la promesa de coincidencia exacta para las CADENAS cubre las culturas que la aplicación entrega, y una cultura negociada en tiempo de ejecución sin catálogo escrito degrada a las cadenas neutras. Sus FORMATOS son los de la cultura de la petición, existan los catálogos que existan, escritos desde los datos que envía el servidor, deterministas y nunca dependientes del navegador; una página sin cultura instalada formatea en la cultura invariante (Desde 0.2.0-preview.61).
Desde 0.2.0-preview.29
La mitad web de esta historia es una petición: una cultura entra, SSR e hidratación coinciden. Una ventana no tiene petición, así que un cambio ahí tiene tres articulaciones que la web nunca tiene: la plataforma decide el locale inicial, alguien lo copia a las estáticas de .NET, y el árbol tiene que RECONSTRUIRSE para que el cambio llegue a los píxeles.
Un componente no dice nada de eso. La sección Language del ejemplo Studio es un CultureSwitcher
corriente, que resuelve ICultureController del contexto y no nombra ninguna plataforma... la misma
clase que renderiza el navegador. Aplicar una cultura escribe las dos estáticas (el par del D13: los
recursos y los formatos son decisiones separadas) y repinta la ventana, así que el cambio aterriza en
el siguiente frame con cada bit de estado de la página intacto.
El camino de captura de pantalla lo renderiza de las dos formas, que es lo que cerró el hito: sin
argumento la ventana muestra el par de la MÁQUINA (una interfaz en inglés con formatos portugueses, en
un Mac configurado así), y con --culture pt-BR muestra Marcado, Ativado, Planilha al lado de
R$ 1.234,50.
Escribir el ejemplo encontró el bug que los tests no podían: el host registraba solo el controlador concreto, así que un componente que pedía la INTERFAZ resolvía null y el switcher no cambiaba nada. Dos registros, una instancia. Una capacidad que nadie resuelve en una ventana de verdad es una capacidad que no funciona.
Desde 0.2.0-preview.28
dotnet new equantic-app ahora genera lo que esta página predica: un Resources/Strings.resx con su
satélite pt-BR y el accesor Designer corriente, un CultureSwitcher en la página de inicio, y toda
la historia en una línea, string.Format(Strings.CountedTimes, _count). El primer proyecto de quien
programa responde en dos idiomas antes de haber escrito código, que es el momento más barato para
aprender que las cadenas de interfaz nunca se escriben a mano. Vea
Primeros pasos para el scaffold completo.
Desde 0.2.0-preview.31
Traducir un sitio y no declarar nunca el grupo de traducción es como un sitio localizado compite en
silencio contra sí mismo. Una sola política para toda la aplicación,
options.UseAlternateLinks(AlternateUrls.PathPrefix(), "en", "pt-BR", "es"), pone el conjunto
completo de rel="alternate" hreflang más x-default en el head de cada página, con las reglas de
reciprocidad y de URL absoluta aplicadas en lugar de dejadas a la aplicación. Vea
Integración con el servidor.
Desde 0.2.0-preview.36
Un sitio público quiere cada traducción en su propia dirección (/pt-BR/pricing) porque es la única
forma que un crawler indexa como traducción y que quien lee puede compartir. Una declaración lo hace:
builder.Services.AddUI(options => options
.ScanAssembly(typeof(Program).Assembly)
.UseCultureRoutes("en", "pt-BR", "es")); // la primera se sirve sin prefijo
…
app.UseRequestLocalization(); // el middleware propio de ASP.NET, ya configurado
app.MapUI();Nada de eso es maquinaria propia del SDK. UseCultureRoutes llena las
RequestLocalizationOptions de la plataforma a partir de esa lista (cultura por defecto, culturas
soportadas, y el RouteDataRequestCultureProvider de ASP.NET puesto primero), y registra una
restricción de ruta culture, así que cada página (una [Page] o un MapPage<T>) se mapea también
en /{culture:culture}/…. A partir de ahí:
-
/pt-BR/pricingse renderiza en pt-BR, y el segmento le gana a la cookie y aAccept-Language: una URL que nombra un idioma es una promesa sobre lo que dice la página. -
Cada href interno de la aplicación lleva el idioma: quien escribe pone
/aboutuna vez y el servidor y el navegador emiten los dos/pt-BR/aboutmientras quien lee está en portugués. UnLink, unTextRunque enlaza y un enlace de Markdown igual (un run y un enlace de Markdown desde 0.2.0-preview.60: conservaban el destino tal como se escribió), y cada uno interno se precalienta al pasar el ratón. Las URLs absolutas, las relativas al protocolo (//cdn…), los anclajes ymailto:se dejan en paz. -
/fr/pricinges un 404, no inglés bajo una dirección francesa: la restricción admite exactamente los prefijos que nombraste. -
Un
/pricingpelado pedido en portugués (la cookie que escribeCultureSwitcher, oAccept-Language) se redirige (302) a/pt-BR/pricing, así que lo que se ve, se comparte y se marca siempre nombra su idioma. La cultura por defecto conserva sus URLs peladas. -
Cambiar de idioma es una navegación:
CultureSwitcherva a la misma página en su otra dirección, por el router, sin recargar. -
hreflangyx-defaultvienen del mismo mapa sin una segunda declaración, y uncanonicalque una página escribió a mano se localiza a la salida, así que nunca apunta a un crawler a la traducción equivocada.
Una aplicación que configura el middleware con la sobrecarga de lambda
(app.UseRequestLocalization(o => …)) construye sus opciones inline y no ve nada de la DI, así que
pásale la misma lista ahí con o.UseCultureRoutes("en", "pt-BR", "es").
Los satélites bajo NativeAOT ya no son una pregunta: un arnés ejecutable
(scripts/verify-aot-satellites.sh) publica un binario nativo contra la biblioteca de componentes y
se niega a pasar a menos que en, pt-BR y es respondan todos con la TRADUCCIÓN, porque un satélite
perdido no revienta, responde en inglés en silencio, así que la comprobación compara contra las
traducciones esperadas.
Todavía por delante: dividir catálogos por página, formas plurales más allá de dos, y que
IHandleMetadata se vuelva a ejecutar en un cambio de cultura. El RTL y la cobertura de tipografía
por escritura son explícitamente otras vías.
🏁 Empieza aquí
🏗️ Arquitectura
- Visión general de la arquitectura
- Componentes write-once
- Superficie declarativa
- Arquitectura de paquetes
- Componentes
- Estilos
- Localización
- Analytics y GTM
📱 Write-once
- Motor Photon
- Design System
- Capacidades
- Almacenamiento
- Formularios
- Editor de código
- Markdown
- Mermaid
- Renderizado de email
⚙️ Compilación
⚡ Runtime
🔌 Servidor
🎨 Ecosistema
🚀 Desarrollo