CJK (Chinese/Japanese/Korean) typography and locale-switching patterns for
Next.js apps using next-intl. Use when: (1) CJK text renders visually too
large compared to Latin text at the same font size, (2) CSS :lang(zh) selectors
override fonts on elements that should stay Latin (e.g., signatures, brand names),
(3) locale switcher with router.push() redirects back to the old locale due to
stale NEXT_LOCALE cookie, (4) CJK headings wrap to multiple lines when they
should fit on one, (5) environment banners or badges appear on locale-prefixed
paths that should be hidden, (6) text-balance CSS causes CJK text to break
unnecessarily. Covers font sizing, lang attribute escape hatch, whitespace-nowrap
vs text-balance, window.location.href for locale switching, and locale-aware
path matching.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
nextjs-cjk-i18n-typography
description
CJK (Chinese/Japanese/Korean) typography and locale-switching patterns for
Next.js apps using next-intl. Use when: (1) CJK text renders visually too
large compared to Latin text at the same font size, (2) CSS :lang(zh) selectors
override fonts on elements that should stay Latin (e.g., signatures, brand names),
(3) locale switcher with router.push() redirects back to the old locale due to
stale NEXT_LOCALE cookie, (4) CJK headings wrap to multiple lines when they
should fit on one, (5) environment banners or badges appear on locale-prefixed
paths that should be hidden, (6) text-balance CSS causes CJK text to break
unnecessarily. Covers font sizing, lang attribute escape hatch, whitespace-nowrap
vs text-balance, window.location.href for locale switching, and locale-aware
path matching.
When adding CJK (Chinese/Japanese/Korean) language support to a Next.js app with
, several non-obvious typography and UX issues arise that don't exist
with Latin-only locales. CJK glyphs are visually larger, denser, and have different
spacing characteristics than Latin characters, requiring locale-aware adjustments
that go beyond simple translation.
next-intl
Context / Trigger Conditions
Next.js app using next-intl with CJK locales (zh-CN, zh-TW, ja, ko)
Custom CJK fonts loaded via CSS :lang(zh) selectors or CSS custom properties
Headings or large text that looks oversized in CJK compared to English
Latin text (signatures, brand names) unexpectedly rendered in CJK font
Locale switcher redirects back to old locale after switching
Short CJK headings wrapping to multiple lines unnecessarily
Route-based visibility logic (hiding elements on certain paths) breaking on locale-prefixed URLs
Solution
Pattern 1: Conditional Font Sizing for CJK
CJK glyphs render ~20-30% visually larger than Latin characters at the same
font-size. Reduce by one Tailwind step at each breakpoint:
Why: CJK fonts (like LXGW WenKai, Noto Sans CJK) have taller x-heights and
wider character bodies than Latin display fonts (like Instrument Serif). Combined
with CJK-specific line-height overrides (e.g., :lang(zh) h1 { line-height: 1.4 }),
headlines can appear dramatically oversized.
Pattern 2: lang Attribute Escape Hatch for Latin Text
When CSS uses :lang(zh) to override fonts, any child element inherits the
language context. Use lang="en" on elements that must always use Latin fonts:
// Signature that should always use Caveat (handwriting font)
<div
lang="en"
className="text-3xl font-handwriting"
style={{ fontFamily: 'var(--font-handwriting)' }}
>
{t('signature')} {/* "Joe" — always Latin text */}
</div>
Why: The lang attribute is inherited in the DOM. If a parent has lang="zh-CN",
then :lang(zh) CSS selectors match all descendants. Adding lang="en" to a
specific element creates a language boundary, preventing CJK font overrides from
applying to Latin text like signatures, brand names, or code snippets.
Pattern 3: whitespace-nowrap vs text-balance for CJK
The CSS text-balance property can force CJK text to wrap unnecessarily because
CJK characters are individually breakable (no word boundaries):
When to use whitespace-nowrap: Short CJK headings (< 15 characters) that
should always display on one line. Combine with reduced font sizes to ensure they
fit within the viewport.
When NOT to use: Long CJK paragraphs or descriptions where wrapping is expected.
Pattern 4: window.location.href for Locale Switching
When using cookie-based locale persistence (NEXT_LOCALE cookie) with next-intl,
router.push() can cause a redirect loop:
// BAD: Cookie may be stale when RSC fetch is constructedconst router = useRouter();
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
router.push(newPath); // Middleware reads OLD cookie value → redirects back// GOOD: Full page reload guarantees fresh cookiesdocument.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
window.location.href = newPath; // New request with updated cookie
Why: document.cookie updates the browser's cookie jar synchronously, but
router.push() may construct the RSC (React Server Component) fetch request
before the cookie propagates to the request headers. The middleware then reads
the stale cookie and redirects back to the old locale. A full page reload
(window.location.href) guarantees the browser sends fresh cookies with the
new request.
Trade-off: Full reload is slower than client-side navigation, but locale
switches are infrequent enough (user action, not automatic) that this is acceptable.
Pattern 5: Locale-Aware Path Matching
When hiding elements on specific paths (e.g., dev badges on landing pages),
strip locale prefixes before matching: