Language direction and accessibility
Language, writing direction, and bidirectional text are related but different.
langtells browsers and assistive technology which language an element contains.dirtells layout and text processing whether the surrounding direction is left-to-right or right-to-left.- Bidirectional isolation prevents an inserted run of text from changing the order of surrounding punctuation and words.
Changing dir alone does not translate text, and translating text does not automatically make every surrounding element use the right language.
Let a real provider own lang and dir
Give <c-i18n> a real tag when a subtree needs semantic language markup:
<c-i18n locale="ar-EG" tag="section">
<c-account-summary />
</c-i18n>
Citry derives the normal direction for ar-EG and renders:
<section lang="ar-EG" dir="rtl">
...
</section>
You may set direction="ltr" or direction="rtl" explicitly for an unusual subtree. In normal application code, let Citry derive it from the locale.
When another framework owns <html>, that framework's document shell should read context.locale and context.direction and apply them to its root element. Citry does not search for and mutate an unrelated document element.
Use logical CSS properties
Components that support both directions should prefer logical properties:
.account-card {
padding-inline-start: 1rem;
border-inline-end: 1px solid var(--border-color);
}
Review icons separately. An arrow that means "next" may need to mirror, while a media-play icon or a brand mark usually should not.
Citry isolates interpolated scalar text
An inserted name, path, identifier, or number may use another direction from the surrounding translation. Citry keeps typed scalar boundaries through message formatting and isolates those runs so they do not reorder surrounding text.
Application strings may not contain Unicode bidi controls or paragraph boundaries inside one inline parameter. Use separate structural markup for multiline or deliberately directed content.
HTML escaping and bidi isolation solve different problems. Escaping prevents markup injection. Isolation protects visual ordering. Citry applies both where each is needed.
Preserve fallback language metadata
tr() returns only text. If fallback selects another language, the plain string cannot attach a different lang to itself.
Server code that permits this fallback should use resolve() and apply the returned metadata to an application-owned element:
resolved = self.i18n.resolve("my-app-legal-notice")
return {
"notice": resolved.text,
"notice_lang": resolved.locale,
"notice_dir": resolved.direction,
}
<p c-lang="notice_lang" c-dir="notice_dir">
{{ notice }}
</p>
resolved.used_fallback tells application code whether the selected locale differs from the requested locale.
Directional isolation still protects a plain fallback string, but it cannot tell a screen reader which language to pronounce. Keep the lang markup when the language may differ.
Translate accessible outputs too
Keep visible and assistive text under one message when they describe the same control:
my-app-account-actions = Actions
.aria-label = Open account actions
.title = Show available actions
Call the attribute explicitly for aria-label or title. Catalog fallback is computed for each attribute, so a missing assistive translation cannot be mistaken for the visible label.
Browser-owned text and accessibility outputs need equivalent-language coverage for every selectable locale. Their call sites cannot safely attach a hidden fallback language after $i18n.switchLocale().
Wrapperless rich messages have the same requirement. See Rich messages for why <c-trans> rejects a cross-language fallback.
Browser providers update their semantic host
A successful browser switch updates the client provider's lang and dir attributes together with its readonly context. It does not change ordinary server-owned text below that element.
Do not put fixed server text directly below a provider whose browser locale can change, because the wrapper could then claim a new language while the text stays in the old one. Place fixed text behind its own server-only <c-i18n tag="..."> boundary or rerender it from the server.