Files
zeekay 0e90bb83c9 @hanzo/i18n 0.5.0: <T> is extracted, format writes figures
The extractor reads the children of <T> as keys. JSX text is normalized
by the compiler before T sees it, so the key is SWC's rule applied to
the source (tools/extract/src/jsx.ts): entities decoded, lines cut at
every terminator, spaces and tabs trimmed where they touch a break
unless an entity wrote them, the rest joined by one space. Each case is
compiled with esbuild and held equal to the children T receives; the
same cases and the entity edges were measured against Next 16.3's SWC.

useI18n().format (and format(locale) from the root) is Intl at the
locale: number, percent, money, date, relative and list, with Intl's
options passed through, so en-US output is toLocaleString's byte for
byte. Right to left every result is one isolate and a list isolates
each item, so Latin names read right to left; isolate leaves a whole
isolate alone, so a figure handed to t carries one pair of marks. The
browser test reads Arabic and ar-EG digits, one isolate per figure,
and the list's order, which fails without the item isolates.

Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
2026-10-22 15:25:09 -07:00

56 lines
2.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRoot } from 'react-dom/client'
import { I18nProvider, LanguagePicker, useI18n } from '../../src/react.ts'
function App({ review }: { review: boolean }) {
const { t, dir, format } = useI18n()
return (
<main>
<LanguagePicker review={review} />
<h1>{t('Welcome')}</h1>
<p id="range">{t('Prefill at {range} tokens a second', { range: '1,150–1,400' })}</p>
<p id="terms">{t('Read the {terms}.', { terms: <a href="/terms">{t('Terms')}</a> })}</p>
<p id="count">{format.number(1234567)}</p>
<p id="total">{t('Total: {amount}', { amount: format.money(1234.5, 'USD') })}</p>
<p id="names">{t('Built with {names}', { names: format.list(['Chat', 'Dev', 'Kai']) })}</p>
<p id="when">{format.relative(Date.now() - 3 * 60000)}</p>
<aside data-side={dir === 'rtl' ? 'right' : 'left'}>{t('Settings')}</aside>
<code>hanzo status</code>
<span id="plain" data-i18n-mirror>→</span>
<section dir="rtl">
<code id="inner">hanzo status</code>
<span id="mirrored" data-i18n-mirror>→</span>
</section>
</main>
)
}
const params = new URL(location.href).searchParams
const user = params.get('user')
createRoot(document.getElementById('root')!).render(
<I18nProvider
userId={user === 'guest' ? null : user ?? 'alice'}
loadCatalog={async (code) => {
if (code === 'fr-FR') {
throw new Error('test network error')
}
if (code === 'ar' || code.startsWith('ar-')) {
return {
Welcome: 'مرحبًا',
Settings: 'الإعدادات',
Language: 'اللغة',
'Prefill at {range} tokens a second': 'ملء مسبق بسرعة {range} رمز في الثانية',
'Read the {terms}.': 'اقرأ {terms}.',
Terms: 'الشروط',
'Total: {amount}': 'المجموع: {amount}',
'Built with {names}': 'مبني باستخدام {names}',
}
}
return { Welcome: `Welcome:${code}` }
}}
>
<App review={params.has('review')} />
</I18nProvider>,
)