eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
START HERE
Getting Started
WRITE-ONCE
Write-Once Components
Declarative Surface
Photon Engine
Design System
Capabilities
Storage
Forms
Code Editor
Markdown
Mermaid
Email Rendering
ARCHITECTURE
Architecture Overview
Package Architecture
Components
Styling
Localization
Analytics & GTM
COMPILATION
Compiler
Compile-Time Evaluation
Supported C# Features
External Type Resolution
Build Flow
Diagnostics
RUNTIME
Runtime (TypeScript)
Performance
SERVER
Server Integration
Assets
BunPackage
Security
ECOSYSTEM
Image
Charts
Icons
Lottie
DEVELOPMENT
Visual Editor
Debug
Roadmap
PT-BR
Analytics-pt-BR
Architecture-pt-BR
Assets-pt-BR
BuildFlow-pt-BR
BunPackage-pt-BR
Capabilities-pt-BR
Charts-pt-BR
CodeEditor-pt-BR
Compiler-pt-BR
CompileTimeEvaluation-pt-BR
Components-pt-BR
Debug-pt-BR
DeclarativeSurface-pt-BR
DesignSystem-pt-BR
Diagnostics-pt-BR
EmailRealizer-pt-BR
ExternalTypeResolution-pt-BR
Forms-pt-BR
GettingStarted-pt-BR
Home-pt-BR
Icons-pt-BR
Image-pt-BR
Localization-pt-BR
Lottie-pt-BR
Markdown-pt-BR
Mermaid-pt-BR
PackageArchitecture-pt-BR
Performance-pt-BR
Photon-pt-BR
Roadmap-pt-BR
Runtime-pt-BR
Security-pt-BR
ServerIntegration-pt-BR
Storage-pt-BR
Styling-pt-BR
SupportedFeatures-pt-BR
VisualEditor-pt-BR
WriteOnceComponents-pt-BR
ACTIONS & INPUTS
Button
IconButton
TextInput
Select
Checkbox
Switch
RadioGroup
SegmentedControl
Slider
Stepper
SearchField
SURFACES & DISPLAY
Card
Badge
Chip
Avatar
Banner
ProgressBar
EmptyState
Divider
NAVIGATION
Tabs
AppBar
BottomNavigation
Breadcrumb
Pagination
PageIndicator
Menu
Drawer
OVERLAYS
Dialog
BottomSheet
Toast
Popover
Tooltip
LISTS & DATA
List
ListView
Table
Accordion
CodeBlock
TOUCH INTERACTION
PullToRefresh
SwipeableRow
Docspt-BR
Localização
Edit this page
10 min read
🌐 Esta página em: English · Português
Multi-idioma no eQuantic.UI segue uma lei: o desenvolvedor localiza com o que o próprio .NET oferece: .resx, o acessor fortemente tipado, CultureInfo. Ele nunca vê um catálogo JavaScript nem uma DSL do framework. O design completo (Track L: como o eqc faz a ponte do ResourceManager até o browser, catálogos por cultura, o subconjunto de formatação, as cercas honestas) vive no repositório em docs/I18N-PLAN.md.
As strings do próprio SDK
Desde 0.2.0-preview.24
Componentes nunca embutem uma string de interface. Todo rótulo interno (os anúncios que um leitor de tela ouve ("Marcado", "Ativado"), as afordâncias de dispensar, o placeholder de busca, a barra de localizar do editor) lê SdkStrings, que hoje é um resx com satélites. Um app com zero resx próprio já anuncia "Marcado" para um leitor de tela em pt-BR: as chaves do SDK entram no catálogo de toda cultura que o app emite.
Quais idiomas viajam, e como levar menos. As strings do SDK estão traduzidas hoje para pt-BR e es, e vão DENTRO do pacote como assemblies satélite (lib/net10.0/<cultura>/eQuantic.UI.Components.resources.dll, cerca de 8 KB cada) — um pacote levando os seus idiomas, como o Roslyn e a plataforma de testes fazem, em vez de um pacote por idioma. Um app que quer só alguns deles não precisa de outro pacote, porque o .NET já é dono dessa decisão:
1
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
Medido num consumidor do pacote publicado: sem isso a saída do app leva es/ e pt-BR/; com isso, nenhum dos dois. O único sítio onde não se aplica é uma referência de PROJETO — os samples deste repositório mantêm os satélites de qualquer forma, porque a propriedade filtra o que chega por pacote.
Janelas Photon adotam o locale da máquina ao abrir
Desde 0.2.0-preview.24
Um processo de interface aberto pelo Finder não carrega LANG/LC_*, então o .NET começa invariante mesmo numa máquina pt-BR. Os shells resolvem a verdade da plataforma antes do primeiro frame: NSLocale.preferredLanguages[0] como cultura de interface e currentLocale como cultura de formato na Apple (a mesma divisão que o .NET modela como CurrentUICulture versus CurrentCulture), Locale.getDefault() alimentando as duas no Android, e o PhotonCultureController copia o par para os statics do processo, repintando num Apply posterior exatamente como o controller de tema repinta numa troca de modo.
Uma página fala resx no web, de ponta a ponta
Desde 0.2.0-preview.26
Um app escreve localização exatamente como qualquer app .NET: um Resources/Strings.resx mais um Strings.pt-BR.resx, os acessores comuns do Designer, string.Format(Strings.Greeting, nome), e a página responde na cultura do request nas DUAS metades do alvo web.
GET /pagina + Accept-Language
escolhe catalogo (exata, pai, neutra)
shell + window.__EQ_CULTURE__
boot instala a cultura
hidrata com as MESMAS strings
Browser
Servidor
Runtime
As peças, espelhando a ponte de tema encaixe por encaixe:
O eqc REESCREVE os acessores, nunca os inline. Strings.Hero_Title compila para $eq.str("Strings", "Hero.Title"), porque fazer inline gravaria a cultura da máquina de build dentro do bundle. A detecção é por FORMA (as propriedades estáticas ResourceManager + Culture do Designer), então qualquer resx em qualquer lugar do projeto funciona, com o nome que o desenvolvedor quiser.
O build emite catálogos a partir das chaves que o app realmente usa. wwwroot/_equantic/strings/neutral.json mais um {cultura}.json por variante autorada, cada um já ACHATADO contra a cadeia de fallback do .NET: o servidor escolhe um arquivo, nunca mescla.
O servidor embute a resposta. O UseRequestLocalization (o APP liga a negociação, o SDK só lê o que o middleware definiu) alimenta o <html lang> e um slot window.__EQ_CULTURE__ = { name, formatName, strings }; o boot o instala ANTES da hidratação, então o cliente resolve exatamente as strings que o servidor renderizou.
Trocar de idioma re-renderiza, nunca recarrega
Desde 0.2.0-preview.27
ICultureController é a mão do app sobre o IDIOMA, na mesma forma do IThemeController, resolvido por nome de interface, então um componente que oferece o seletor nunca aprende qual alvo respondeu. Ele fala nomes BCP-47, nunca CultureInfo: o contrato atravessa para um browser onde esse tipo não existe, e um nome é a única moeda que .NET, web e as duas plataformas móveis compartilham.
Web: o setCulture busca o catálogo da cultura só quando ele ainda não está em memória, escolhendo o arquivo do mesmo jeito que o SERVIDOR escolhe (exata → pais → neutra), troca, e re-renderiza pelo mesmo agendador que o SetState usa. O estado sobrevive; só o texto que mudou é tocado. A troca também sobrevive à página: o controller escreve o cookie de cultura **do próprio ASP.NET**, no formato dele, então o próximo request já chega traduzido.
Nativo: o PhotonCultureController realiza a mesma interface: os statics do processo mudam e a janela repinta.
CultureSwitcher é a superfície embutida: um segmented control para dois ou três idiomas, um menu acima disso, acendendo a opção ativa por nome exato e depois pela parte do idioma (uma página servida como pt-BR precisa acender uma entrada pt).
1
2
3
4
5
new CultureSwitcher([
new("en", "English"),
new("pt-BR", "Português"),
new("es", "Español"),
])
Um especificador de formato sobrevive à viagem
Desde 0.2.0-preview.27
{0:C2} escrito uma vez em C# imprime R$ 1.234,50 numa página pt-BR e 1234,50 € numa es, com SSR e cliente concordando caractere por caractere, e uma troca de cultura reformatando ao vivo junto com as strings.
A prova é o método: a fixture pinada é gerada do .NET real (value.ToString(spec, culture), três culturas escolhidas para discordar) e o runtime transpilado precisa reproduzi-la exatamente. Essa direção pegou todas as divergências em tempo de build. O Intl arredonda o meio para longe do zero onde o .NET arredonda para o par, então o formatador pré-arredonda com o helper banqueiro; os presets do Intl derrubam a data curta de en-US para ano de dois dígitos, então os padrões próprios de cada cultura viajam no catálogo dela, junto com o código ISO da moeda (o Intl exige um código, e nenhuma API do browser deriva um a partir do locale).
O que não consegue concordar é recusado no build, nunca aproximado: EQ2100 rejeita alinhamento e especificadores fora do subconjunto num template resx; EQ2101 confere os placeholders de cada TRADUÇÃO contra o resx neutro, então uma string pt-BR que pede {2} onde a neutra tem {0}/{1} falha o build em vez de estourar só para os leitores brasileiros.
Nativo: satélites provados sob AOT
O Photon roda .NET de verdade, sem transpilação: ResourceManager e assemblies satélite já funcionam lá: um app nativo localiza hoje com resx comum. O risco não é o mecanismo, é o EMPACOTAMENTO: um publish com trimming/NativeAOT que derruba os satélites não quebra, ele responde inglês em silêncio. Por isso a prova é executável (scripts/verify-aot-satellites.sh): um binário NativeAOT que referencia a biblioteca só passa se en/pt-BR/es responderem com a TRADUÇÃO, e se es-AR cair em es pelo caminho de pai real.
Duas culturas numa janela
Desde 0.2.0-preview.29
A metade web desta história é uma requisição: uma cultura entra, SSR e hidratação concordam. Uma janela não tem requisição, então uma troca ali tem três juntas que o web nunca tem... a plataforma decide o locale inicial, alguém o copia para os statics do .NET, e a árvore precisa RECONSTRUIR para a mudança alcançar os pixels.
Um componente não diz nada disso. A seção Language do sample Studio é um CultureSwitcher comum, que resolve o ICultureController pelo contexto e não nomeia plataforma nenhuma... a mesma classe que o browser renderiza. Aplicar uma cultura escreve os dois statics (o par do D13: recursos e formatos são decisões separadas) e repinta a janela, então a troca cai no frame seguinte com todo o estado da página intacto.
O caminho de screenshot a renderiza dos dois jeitos, que foi o que fechou o marco: sem argumento a janela mostra o par da MÁQUINA (interface em inglês com formatos portugueses, num Mac configurado assim), e com --culture pt-BR ela mostra Marcado, Ativado, Planilha ao lado de R$ 1.234,50.
Escrever o sample achou o bug que os testes não achavam: o host registrava só o controller concreto, então um componente que pedia a INTERFACE resolvia nulo e o switcher não trocava nada. Dois registros, uma instância. Uma capacidade que ninguém resolve numa janela de verdade é uma capacidade que não funciona.
Um app novo nasce localizado
Desde 0.2.0-preview.28
O dotnet new equantic-app agora scaffolda o que esta página prega: um Resources/Strings.resx com o satélite pt-BR e o acessor comum do Designer, um CultureSwitcher na página inicial, e a história inteira numa linha, string.Format(Strings.CountedTimes, _count). O primeiro projeto de uma pessoa responde em dois idiomas antes de ela escrever qualquer código, que é o momento mais barato de aprender que string de interface nunca se fixa no código. Veja Primeiros passos para o scaffold completo.
Um buscador sabe qual URL é qual idioma
Desde 0.2.0-preview.31
Traduzir um site e nunca declarar o grupo de tradução é como um site localizado passa a competir contra si mesmo em silêncio. Uma política do app inteiro, options.UseAlternateLinks(AlternateUrls.PathPrefix(), "en", "pt-BR", "es"), coloca o conjunto rel="alternate" hreflang inteiro mais o x-default no head de toda página, com as regras de reciprocidade e de URL absoluta garantidas em vez de deixadas para o app. Veja Integração com o servidor.
O idioma viaja na URL
Desde 0.2.0-preview.36
Um site público quer cada tradução no seu próprio endereço — /pt-BR/pricing — porque é a única forma que um buscador indexa como tradução e que um leitor consegue compartilhar. Uma declaração resolve:
1
2
3
4
5
6
builder.Services.AddUI(options => options
.ScanAssembly(typeof(Program).Assembly)
.UseCultureRoutes("en", "pt-BR", "es")); // a primeira é servida sem prefixo
app.UseRequestLocalization(); // o middleware do próprio ASP.NET, já configurado
app.MapUI();
Nada ali é maquinaria própria do SDK. UseCultureRoutes preenche as RequestLocalizationOptions da plataforma a partir dessa lista — cultura padrão, culturas suportadas, e o RouteDataRequestCultureProvider do ASP.NET em primeiro lugar — e registra uma route constraint culture, de modo que toda página (um [Page] ou um MapPage<T>) também é mapeada em /{culture:culture}/…. A partir daí:
/pt-BR/pricing renderiza em pt-BR, e o segmento vence o cookie e o Accept-Language: uma URL que nomeia um idioma é uma promessa sobre o que a página diz.
Todo href interno carrega o idioma — o autor escreve /about uma vez e servidor e navegador emitem /pt-BR/about enquanto o leitor está em português. Link e links de Markdown igual; URLs absolutas, âncoras e mailto: ficam intactos.
/fr/pricing é 404, não inglês num endereço francês: a constraint aceita exatamente os prefixos que você nomeou.
Um /pricing nu pedido em português (o cookie que o CultureSwitcher grava, ou o Accept-Language) é redirecionado (302) para /pt-BR/pricing, para que o que se vê, compartilha e salva sempre nomeie o seu idioma. A cultura padrão mantém as URLs nuas.
Trocar de idioma é uma navegação: o CultureSwitcher vai para a mesma página no outro endereço, pelo router, sem reload.
hreflang e x-default saem do mesmo mapa sem segunda declaração, e um canonical que a página escreveu à mão é localizado na saída, para nunca apontar o buscador para a tradução errada.
Um app que configura o middleware pela sobrecarga com lambda (app.UseRequestLocalization(o => …)) monta as opções inline e não vê nada da DI — passe a mesma lista ali com o.UseCultureRoutes("en", "pt-BR", "es").
Cercas honestas
Chave ausente nunca lança. Renderiza a chave e avisa uma vez. As chaves do próprio SDK têm uma rede a mais: o inglês neutro viaja no runtime como dado gerado, então uma página sem catálogo nenhum lê "Search…" em vez de "SearchPlaceholder".
Uma cultura negociada sem catálogo autorado cai para os fatos NEUTROS (formatos invariantes, determinísticos, nunca dependentes do browser).
Plurais em v1 são chaves explícitas (correto para as línguas de 2 formas); 3+ formas é uma cerca documentada, não uma resposta errada em silêncio.
RTL e cobertura de scripts (CJK, árabe) são outros trilhos, explicitamente.
O fluxo do tradutor é .resx/XLIFF, e o framework não vai crescer uma UI de tradução.