Author:
    Creation:2026-09-26Last update:2026-09-26

    2026 рдореЗрдВ Lingui рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдкрдиреЗ TanStack Start рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХрд╛ рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдХреИрд╕реЗ рдХрд░реЗрдВ

    рд╡рд┐рд╖рдп рд╕реВрдЪреА

    Lingui рдХреНрдпрд╛ рд╣реИ?

    Lingui рдПрдХ i18n рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╣реИ рдЬреЛ macros рдФрд░ message extraction рдХреЗ рдЗрд░реНрдж-рдЧрд┐рд░реНрдж рдмрдирд╛рдИ рдЧрдИ рд╣реИред рдЖрдк рд╕реНрд░реЛрдд рдЯреЗрдХреНрд╕реНрдЯ рдХреЛ рд╕реАрдзреЗ рдЕрдкрдиреЗ рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдореЗрдВ рд▓рд┐рдЦрддреЗ рд╣реИрдВ ( t`Hello` , <Trans>Hello</Trans>), lingui extract рдкреНрд░рддреНрдпреЗрдХ рд╕рдВрджреЗрд╢ рдХреЛ рдХреИрдЯрд▓реЙрдЧ (рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ PO рдлрд╝рд╛рдЗрд▓реЗрдВ) рдореЗрдВ рдПрдХрддреНрд░ рдХрд░рддрд╛ рд╣реИ, рдЕрдиреБрд╡рд╛рджрдХ рдЙрдиреНрд╣реЗрдВ рднрд░рддреЗ рд╣реИрдВ, рдФрд░ Vite рдкреНрд▓рдЧрдЗрди рдЙрдиреНрд╣реЗрдВ рдХреЙрдореНрдкреИрдХреНрдЯ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ рд╕рдВрдХрд▓рд┐рдд (compile) рдХрд░рддрд╛ рд╣реИред рд╕рдВрджреЗрд╢ ICU MessageFormat рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП plurals рдФрд░ selects рд╕рдорд░реНрдерд┐рдд рд╣реИрдВред

    TanStack Start рдореЗрдВ рдХреЛрдИ рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд i18n рд▓реЗрдпрд░ рдирд╣реАрдВ рдЖрддреА рд╣реИ, рдЗрд╕рд▓рд┐рдП рдпрд╣ рдЧрд╛рдЗрдб рд╢реБрд░реВ рд╕реЗ Lingui рдХреЛ рдЗрд╕рдореЗрдВ рдПрдХреАрдХреГрдд рдХрд░рддреА рд╣реИ:

    • Babel рджреНрд╡рд╛рд░рд╛ рд╕рдВрдХрд▓рд┐рдд Macros @rolldown/plugin-babel рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ (@vitejs/plugin-react v6 рдФрд░ Vite 8 рдХреЗ рд╕рд╛рде рдЖрд╡рд╢реНрдпрдХ)ред
    • Locale routing рдПрдХ рд╡реИрдХрд▓реНрдкрд┐рдХ {-$locale} рд╕реЗрдЧрдореЗрдВрдЯ рдХреЗ рд╕рд╛рде (/about, /fr/about)ред
    • рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ рдХреИрдЯрд▓реЙрдЧ, рдорд╛рдВрдЧ рдкрд░ рд▓реЛрдб рдХрд┐рдпрд╛ рдЧрдпрд╛ (on demand), рдФрд░ рдкреНрд░рддрд┐ рд░реЗрдВрдбрд░ рдПрдХ I18n рдЗрдВрд╕реНрдЯреЗрдВрд╕ рддрд╛рдХрд┐ рд╕рдорд╡рд░реНрддреА (concurrent) SSR рдЕрдиреБрд░реЛрдз рдХрднреА рднреА рд▓реЛрдХреЗрд▓ рд╕рд╛рдЭрд╛ рди рдХрд░реЗрдВред
    • рд╕рдВрдкреВрд░реНрдг рдмрд╣реБрднрд╛рд╖реА SEO: рдЕрдиреБрд╡рд╛рджрд┐рдд <title> рдФрд░ рд╡рд┐рд╡рд░рдг, рдХреИрдиреЛрдирд┐рдХрд▓ URL, x-default рдХреЗ рд╕рд╛рде hreflang, Open Graph рд▓реЛрдХреЗрд▓реНрд╕, JSON-LD, рд╕рд╛рдЗрдЯрдореИрдк, robots.txt, рдкреНрд░реА-рд░реЗрдВрдбрд░рд┐рдВрдЧ рдФрд░ рд╕реНрдерд╛рдиреАрдпрдХреГрдд 404 рдкреЗрдЬред
    рдХреНрдпрд╛ рдЖрдк рдХрд┐рд╕реА рдЕрдиреНрдп рд╕реНрдЯреИрдХ рдХреА рддрд▓рд╛рд╢ рдореЗрдВ рд╣реИрдВ? TanStack Start + use-intl рдЧрд╛рдЗрдб, TanStack Start + Paraglide рдЧрд╛рдЗрдб, рдпрд╛ TanStack Start + Intlayer рдЧрд╛рдЗрдб рджреЗрдЦреЗрдВред
    рдХреНрдпрд╛ рдЖрдк Next.js рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ? Next.js + Lingui рдЧрд╛рдЗрдб рджреЗрдЦреЗрдВред рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреА рддреБрд▓рдирд╛ рдХрд░ рд░рд╣реЗ рд╣реИрдВ? Lingui рдмрдирд╛рдо Intlayer рдкрдврд╝реЗрдВред

    TanStack Start рдкрд░ Lingui рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмреЗрдВрдЪрдорд╛рд░реНрдХ рдХреНрдпрд╛ рдХрд╣рддрд╛ рд╣реИ

    i18n рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд╣рд░ рдкреНрд░рдореБрдЦ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рд╕рд╛рде рд╕рдорд╛рди 10-рдкреЗрдЬ, 10-рд▓реЛрдХреЗрд▓ TanStack Start рдРрдк рдЪрд▓рд╛рддрд╛ рд╣реИ рдФрд░ рдорд╛рдкрддрд╛ рд╣реИ рдХрд┐ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХреНрдпрд╛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рддрд╛ рд╣реИред

    рдбрд╛рдпрдирд╛рдорд┐рдХ JSON рд▓реЛрдбрд┐рдВрдЧ

    рд░рдирдЯрд╛рдЗрдо рдкрд░ рдЖрд▓рд╕реНрдп рд╕реЗ рдЕрдиреБрд╡рд╛рдж рд▓реЛрдб рдХрд░рддрд╛ рд╣реИ

    рд╕реНрдХреЛрдкреНрдб JSON (рдиреЗрдорд╕реНрдкреЗрд╕рд┐рдВрдЧ)

    рдкреНрд░рддрд┐-рдкреГрд╖реНрда рдЕрдиреБрд╡рд╛рдж рдиреЗрдорд╕реНрдкреЗрд╕

    I18n рдкреНрд░рджрд░реНрд╢рди рдмреЗрдВрдЪрдорд╛рд░реНрдХ

    рдпрд╣ рдореАрдЯреНрд░рд┐рдХ рдХреНрдпрд╛ рд╣реИ?

    рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдмрдВрдбрд▓ рдХрд╛ рдХреБрд▓ gzip-рд╕рдВрдкреАрдбрд╝рд┐рдд рдЖрдХрд╛рд░ред рдЗрд╕рдореЗрдВ рдХреЗрд╡рд▓ рдЯреНрд░реА-рд╢реЗрдХрд┐рдВрдЧ рдФрд░ рдорд┐рдирд┐рдлрд┐рдХреЗрд╢рди рдХреЗ рдмрд╛рдж рдкреНрд░рджрд╛рддрд╛ рдФрд░ рд╕рд╛рдордЧреНрд░реА рдкреБрдирд░реНрдкреНрд░рд╛рдкреНрддрд┐ рддрд░реНрдХ рд╢рд╛рдорд┐рд▓ рд╣реИрдВред

    рдпрд╣ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдХреНрдпреЛрдВ рд╣реИ?

    рдПрдХ рдЫреЛрдЯрд╛ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдЖрдХрд╛рд░ рдкреНрд░рд╛рд░рдВрднрд┐рдХ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдкреЗрд▓реЛрдб рдХреЛ рдХрдо рдХрд░рддрд╛ рд╣реИ, рдЬрд┐рд╕рд╕реЗ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рддреЗрдЬрд╝ рдбрд╛рдЙрдирд▓реЛрдб рдФрд░ рдирд┐рд╖реНрдкрд╛рджрди рд╕рдордп рд╣реЛрддрд╛ рд╣реИред

    рджреЗрдЦрдиреЗ рдХрд╛ рддрд░реАрдХрд╛

    @lingui/core@6.6.0 рдХреЗ рд▓рд┐рдП рдореБрдЦреНрдп рдЖрдВрдХрдбрд╝реЗ, 2026-09-26 рдХреЛ рдорд╛рдкреЗ рдЧрдП (gzip):

    рд╕реЗрдЯрдЕрдкрд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЖрдХрд╛рд░рдкреНрд░рддрд┐ рдкреЗрдЬ JSрдЕрдиреНрдп рд▓реЛрдХреЗрд▓ рд▓реАрдХрдЕрдиреНрдп рдкреЗрдЬ рд▓реАрдХ
    рдХреЛрдИ i18n рдирд╣реАрдВ (рдмреЗрд╕ рдРрдк)-111.0 KB0%0%
    Lingui (рдЗрд╕ рдЧрд╛рдЗрдб рдХрд╛ рд╕реЗрдЯрдЕрдк)56.7 KB115.2 KB9.3%0%
    @intlayer/lingui (рд╕рдВрдЧрддрддрд╛)9.8 KB136.7 KB9.9%0%
    react-intlayer (рдореВрд▓ Intlayer)4.5 KB126.8 KB0%0%

    рдореБрдЦреНрдп рдирд┐рд╖реНрдХрд░реНрд╖:

    • рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдХреЗрд╡рд▓ рдПрдХ рдХреИрдЯрд▓реЙрдЧ рдорд╛рдВрдЧ рдкрд░ рд▓реЛрдб рдХрд░реЗрдВред рдпрд╣ рдкреЗрдЬреЛрдВ рдХреЛ рдмреЗрд╕ рдРрдк рдЖрдХрд╛рд░ рдХреЗ рдХрд░реАрдм рд░рдЦрддрд╛ рд╣реИред
    • рд░рдирдЯрд╛рдЗрдо рднрд╛рд░реА рд░рд╣рддрд╛ рд╣реИ (~57 KB gzip)ред @intlayer/lingui рд╕рдВрдЧрддрддрд╛ рдПрдбреЗрдкреНрдЯрд░ (рдЪрд░рдг 16) рдЖрдкрдХреЗ рдореИрдХреНрд░реЛрдЬрд╝ рдХреЛ рдмрдирд╛рдП рд░рдЦрддрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ рдШрдЯрд╛рдХрд░ ~10 KB рдХрд░ рджреЗрддрд╛ рд╣реИред
    рдкреВрд░рд╛ рдбреЗрдЯрд╛ рджреЗрдЦреЗрдВ: TanStack Start рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд░рд┐рдкреЛрд░реНрдЯ, рдФрд░ рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реАред

    TanStack Start рдкрд░ рдлреАрдЪрд░ рддреБрд▓рдирд╛

    TanStack Start рдкрд░ рдЖрдорддреМрд░ рдкрд░ рдЙрдкрдпреЛрдЧ рдХреА рдЬрд╛рдиреЗ рд╡рд╛рд▓реА рдЕрдиреНрдп рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреЗ рд╕рд╛рде Lingui рдХреА рддреБрд▓рдирд╛:

    рдлреАрдЪрд░react-intlayer (Intlayer)use-intlParaglide JSLingui
    рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдХреЗ рдкрд╛рд╕ рдЕрдиреБрд╡рд╛рджтЬЕ рд╕рд╣-рд╕реНрдерд┐рдд (Co-located)тЭМ рдХреЗрдВрджреНрд░реАрдХреГрдд JSONтЭМ рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ JSON рдлрд╝рд╛рдЗрд▓тЪая╕П рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдореЗрдВ рд╕реНрд░реЛрдд рдЯреЗрдХреНрд╕реНрдЯ
    TypeScript рдПрдХреАрдХрд░рдгтЬЕ рд╕реНрд╡рддрдГ рдЬрдирд░реЗрдЯ рдХрд┐рдП рдЧрдП рдкреНрд░рдХрд╛рд░тЬЕ AppConfig рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗтЬЕ рдЯрд╛рдЗрдк рдХрд┐рдП рдЧрдП рд╕рдВрджреЗрд╢ рдлрд╝рдВрдХреНрд╢рдитЪая╕П рдХреЗрд╡рд▓ Macros
    рд▓рд╛рдкрддрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдирд╛тЬЕ рдЯрд╛рдЗрдк рддреНрд░реБрдЯрд┐рдпрд╛рдВ рдФрд░ рдмрд┐рд▓реНрдб рдЪреЗрддрд╛рд╡рдирд┐рдпрд╛рдВтЪая╕П рд░рдирдЯрд╛рдЗрдо рдлрд╝реЙрд▓рдмреИрдХтЪая╕П рдмреЗрд╕ рд▓реЛрдХреЗрд▓ рдкрд░ рдлрд╝реЙрд▓рдмреИрдХ рдХрд░рддрд╛ рд╣реИтЪая╕П рд╕реНрд░реЛрдд рдЯреЗрдХреНрд╕реНрдЯ рдкрд░ рдлрд╝реЙрд▓рдмреИрдХ рдХрд░рддрд╛ рд╣реИ
    рд░рд┐рдЪ рдХрдВрдЯреЗрдВрдЯ (JSX, Markdown)тЬЕ рдкреНрд░рддреНрдпрдХреНрд╖ рд╕рдорд░реНрдердитЪая╕П t.rich рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЯреИрдЧтЪая╕П рд╕реНрдЯреНрд░рд┐рдВрдЧреНрд╕тЬЕ <Trans> рдХреЗ рдЕрдВрджрд░ JSX
    рд╕реНрдерд╛рдиреАрдпрдХреГрдд рд░реВрдЯрд┐рдВрдЧтЬЕ рдЕрдВрддрд░реНрдирд┐рд╣рд┐рддтЭМ рдореИрдиреНрдпреБрдЕрд▓ {-$locale}тЬЕ urlPatterns + рд░рд╛рдЙрдЯрд░ рд░реАрд░рд╛рдЗрдЯтЭМ рдореИрдиреНрдпреБрдЕрд▓ {-$locale}
    рд░реАрд▓реЛрдб рдХрд┐рдП рдмрд┐рдирд╛ рд▓реЛрдХреЗрд▓ рд╕реНрд╡рд┐рдЪтЬЕ рд╣рд╛рдБтЬЕ рд╣рд╛рдБтЭМ рдкреВрд░рд╛ рдкреЗрдЬ рд░реАрд▓реЛрдбтЬЕ рд╣рд╛рдБ
    рдмрд╣реБрд╡рдЪрди (Pluralization)тЬЕ рдЧрдгрдирд╛ рдЖрдзрд╛рд░рд┐рддтЬЕ ICUтЬЕ рд╡реЗрд░рд┐рдПрдВрдЯреНрд╕тЬЕ ICU
    ICU MessageFormatтЬЕ format: "icu" рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗтЬЕ рдиреЗрдЯрд┐рд╡тЪая╕П inlang рдкреНрд▓рдЧрдЗрди рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗтЬЕ рдиреЗрдЯрд┐рд╡
    рдХрдВрдЯреЗрдВрдЯ рдкреНрд░рд╛рд░реВрдктЬЕ .ts, .json, .md, .yaml...тЪая╕П .jsonтЪая╕П inlang JSONтЬЕ PO, JSON, CSV
    AI рдЕрдиреБрд╡рд╛рджтЬЕ рдЖрдкрдХрд╛ рдЕрдкрдирд╛ рдкреНрд░рджрд╛рддрд╛ рдФрд░ рдХреБрдВрдЬреАтЭМ рдирд╣реАрдВтЭМ рдирд╣реАрдВтЭМ рдирд╣реАрдВ
    рд╡рд┐рдЬреБрдЕрд▓ рдПрдбрд┐рдЯрд░ / CMSтЬЕ рд╕реНрдерд╛рдиреАрдп рдПрдбрд┐рдЯрд░ + рд╡реИрдХрд▓реНрдкрд┐рдХ CMSтЭМ рдмрд╛рд╣рд░реА рдкреНрд▓реЗрдЯрдлреЙрд░реНрдотЪая╕П inlang рдЗрдХреЛрд╕рд┐рд╕реНрдЯрдо рдРрдкреНрд╕тЭМ рдмрд╛рд╣рд░реА рдкреНрд▓реЗрдЯрдлреЙрд░реНрдо
    SEO рд╕рд╣рд╛рдпрдХ (hreflang, sitemap)тЬЕ рдЕрдВрддрд░реНрдирд┐рд╣рд┐рддтЭМ рдореИрдиреНрдпреБрдЕрд▓тЪая╕П рд╕реНрдерд╛рдиреАрдпрдХреГрдд URLs, рдмрд╛рдХреА рдореИрдиреНрдпреБрдЕрд▓тЭМ рдореИрдиреНрдпреБрдЕрд▓
    рд░рдирдЯрд╛рдЗрдо рдЖрдХрд╛рд░ (gzip, benchmark)4.5 KB75.9 KB1.8 KB56.7 KB
    рд▓реАрдХ, рд╕рд░реНрд╡рд╢реНрд░реЗрд╖реНрда рд╕реЗрдЯрдЕрдк (рд▓реЛрдХреЗрд▓ / рдкреЗрдЬ)0% / 0%0% / 0%49.7% / 0%8.6% / 0%
    CI рдореЗрдВ рд▓рд╛рдкрддрд╛ рдЕрдиреБрд╡рд╛рджтЬЕ npx intlayer testтЪая╕П рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд рдирд╣реАрдВтЪая╕П рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд рдирд╣реАрдВтЬЕ lingui compile --strict
    рд░рдирдЯрд╛рдЗрдо рдЖрдХрд╛рд░ рдФрд░ рд▓реАрдХ рдХреЗ рдЖрдВрдХрдбрд╝реЗ TanStack Start рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд╕реЗ рд▓рд┐рдП рдЧрдП рд╣реИрдВред рд▓реАрдХ рдкреНрд░рддреНрдпреЗрдХ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рд╕рд░реНрд╡реЛрддреНрддрдо рд╕реЗрдЯрдЕрдк рдкрд░ рдорд╛рдкрд╛ рдЬрд╛рддрд╛ рд╣реИред
    рдЕрдиреНрдп TanStack Start рдЧрд╛рдЗрдб: use-intl, Paraglide JS, рдФрд░ Intlayerред

    рдЕрднреНрдпрд╛рд╕ рдЬрд┐рдирдХрд╛ рдЖрдкрдХреЛ рдкрд╛рд▓рди рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП

    • <html> рдкрд░ lang рдФрд░ dir рд╕реЗрдЯ рдХрд░реЗрдВ рд░реВрдЯ рд▓реЛрдХреЗрд▓ рд╕реЗ, рддрд╛рдХрд┐ рд╡реЗ рд╕рд░реНрд╡рд░ HTML рдореЗрдВ рд╕рд╣реА рд╣реЛрдВред
    • рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреЗ рд▓рд┐рдП рдПрдХ URL рд░рдЦреЗрдВ рдкреНрд░реАрдлрд╝рд┐рдХреНрд╕ рдХреЗ рд╕рд╛рде, рддрд╛рдХрд┐ рдкреНрд░рддреНрдпреЗрдХ рднрд╛рд╖рд╛ рд╕рдВрд╕реНрдХрд░рдг рдЕрдиреБрдХреНрд░рдорд┐рдд (indexable) рд╣реЛ рд╕рдХреЗред
    • рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ I18n рдЗрдВрд╕реНрдЯреЗрдВрд╕ рдмрдирд╛рдПрдВ, SSR рдХреЗ рджреМрд░рд╛рди рдХрднреА рднреА рд╡реИрд╢реНрд╡рд┐рдХ рдЗрдВрд╕реНрдЯреЗрдВрд╕ рдХреЛ рдореНрдпреВрдЯреЗрдЯ рди рдХрд░реЗрдВ: рджреЛ рд╕рдорд╡рд░реНрддреА рдЕрдиреБрд░реЛрдз рдПрдХ-рджреВрд╕рд░реЗ рдХреЗ рд▓реЛрдХреЗрд▓ рдХреЛ рдУрд╡рд░рд░рд╛рдЗрдЯ рдХрд░ рджреЗрдВрдЧреЗред
    • рдХреЗрд╡рд▓ рд╕рдХреНрд░рд┐рдп рдХреИрдЯрд▓реЙрдЧ рд▓реЛрдб рдХрд░реЗрдВ, рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛрдб рдореЗрдВ рдЙрди рд╕рднреА рдХреЛ рдХрднреА рдЗрдореНрдкреЛрд░реНрдЯ рди рдХрд░реЗрдВред
    • рдПрдХ рдореИрдХреНрд░реЛ рд╢реИрд▓реА рдЪреБрдиреЗрдВ (рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдореЗрдВ useLingui + t, рд▓реЗрдЬрд╝реА рдбрд┐рд╕реНрдХреНрд░рд┐рдкреНрдЯрд░ рдХреЗ рд▓рд┐рдП msg) рдФрд░ рдЙрд╕реА рдкрд░ рдЯрд┐рдХреЗ рд░рд╣реЗрдВред t, i18n._, i18n.t рдФрд░ <Trans> рдХреЛ рдорд┐рд▓рд╛рдиреЗ рд╕реЗ рдХреЛрдб рдХреЛ рдЗрдВрд╕рд╛рдиреЛрдВ рдФрд░ AI рд╕рд╣рд╛рдпрдХреЛрдВ рдХреЗ рд▓рд┐рдП рдкрдврд╝рдирд╛ рдХрдард┐рди рд╣реЛ рдЬрд╛рддрд╛ рд╣реИред
    • CI рдореЗрдВ lingui extract рдЪрд▓рд╛рдПрдВ рддрд╛рдХрд┐ рдХреЛрдИ рдирдпрд╛ рд╕рдВрджреЗрд╢ рдХрднреА рднреА рдмрд┐рдирд╛ рдЕрдиреБрд╡рд╛рдж рдХреЗ рди рдЬрд╛рдПред
    • рдЕрдкрдиреЗ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВ, рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдкрд░ canonical, hreflang рдФрд░ x-default рдШреЛрд╖рд┐рдд рдХрд░реЗрдВред
    • рдПрдХ рдмрд╣реБрднрд╛рд╖реА рд╕рд╛рдЗрдЯрдореИрдк рдФрд░ robots.txt рдмрдирд╛рдПрдВ, рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреЛ рдкреНрд░реА-рд░реЗрдВрдбрд░ рдХрд░реЗрдВред
    • рд▓реЛрдХреЗрд▓ рд╕реНрд╡рд┐рдЪрд░ рдХреЗ рд▓рд┐рдП рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд▓рд┐рдВрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ, рддрд╛рдХрд┐ рдХреНрд░реЙрд▓рд░реНрд╕ рд╣рд░ рднрд╛рд╖рд╛ рдХреА рдЦреЛрдЬ рдХрд░ рд╕рдХреЗрдВред
    рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдФрд░ SEO рдкрд░ рд╣рдорд╛рд░реА рдЧрд╛рдЗрдб рдФрд░ hreflang рдЧрд╛рдЗрдб рджреЗрдЦреЗрдВред

    TanStack Start рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ Lingui рд╕реЗрдЯ рдЕрдк рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЪрд░рдг-рджрд░-рдЪрд░рдг рдЧрд╛рдЗрдб

    рдпрд╣рд╛рдБ рд╡рд╣ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд╕рдВрд░рдЪрдирд╛ рд╣реИ рдЬрд┐рд╕реЗ рд╣рдо рдмрдирд╛рдПрдВрдЧреЗ:

    bash
    .
    тФЬтФАтФА lingui.config.ts
    тФЬтФАтФА vite.config.ts
    тФФтФАтФА src
        тФЬтФАтФА locales
        тФВ   тФЬтФАтФА en
        тФВ   тФВ   тФФтФАтФА messages.po     # Generated by `lingui extract`
        тФВ   тФЬтФАтФА fr
        тФВ   тФВ   тФФтФАтФА messages.po
        тФВ   тФФтФАтФА es
        тФВ       тФФтФАтФА messages.po
        тФЬтФАтФА start.ts                # Request middleware (locale redirect)
        тФЬтФАтФА i18n
        тФВ   тФЬтФАтФА config.ts           # Locales, URL helpers
        тФВ   тФЬтФАтФА lingui.ts           # Catalog loader, I18n instances
        тФВ   тФЬтФАтФА negotiateLocale.ts  # Accept-Language parsing
        тФВ   тФФтФАтФА seo.ts              # head() builder
        тФЬтФАтФА components
        тФВ   тФЬтФАтФА LocaleSwitcher.tsx
        тФВ   тФЬтФАтФА LocalizedLink.tsx
        тФВ   тФФтФАтФА NotFound.tsx
        тФФтФАтФА routes
            тФЬтФАтФА __root.tsx
            тФЬтФАтФА sitemap[.]xml.ts
            тФЬтФАтФА robots[.]txt.ts
            тФФтФАтФА {-$locale}
                тФЬтФАтФА route.tsx       # Locale layout + I18nProvider
                тФЬтФАтФА index.tsx
                тФЬтФАтФА about.tsx
                тФФтФАтФА $.tsx           # Localized 404
    
    1. рдирд┐рд░реНрднрд░рддрд╛рдПрдБ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ

      bash
      npm install @lingui/core @lingui/react
      npm install -D @lingui/cli @lingui/vite-plugin @lingui/babel-plugin-lingui-macro @lingui/format-po @rolldown/plugin-babel
      
      • @lingui/core / @lingui/react: рд░рдирдЯрд╛рдЗрдо, I18nProvider рдФрд░ рдореИрдХреНрд░реЛрдЬрд╝ (@lingui/core/macro, @lingui/react/macro)ред
      • @lingui/cli: рдХреИрдЯрд▓реЙрдЧ рдореЗрдВ рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рдПрдХрддреНрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП lingui extractред
      • @lingui/vite-plugin: рдЗрдореНрдкреЛрд░реНрдЯ рдкрд░ .po рдХреИрдЯрд▓реЙрдЧ рдХреЛ рдХрдВрдкрд╛рдЗрд▓ рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП lingui compile рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реЛрддреА рд╣реИред
      • @lingui/babel-plugin-lingui-macro + @rolldown/plugin-babel: рдмрд┐рд▓реНрдб рд╕рдордп рдкрд░ рдореИрдХреНрд░реЛрдЬрд╝ рдХреЛ рдЯреНрд░рд╛рдВрд╕рдлрд╝реЙрд░реНрдо рдХрд░рддреЗ рд╣реИрдВред
    2. рдЕрдкрдиреЗ рд▓реЛрдХреЗрд▓ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХреЛ рдХреЗрдВрджреНрд░реАрдХреГрдд рдХрд░реЗрдВ

      рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд▓реЛрдХреЗрд▓ рдмрд┐рдирд╛ рдкреНрд░реАрдлрд╝рд┐рдХреНрд╕ рдХреЗ рд░рд╣рддрд╛ рд╣реИ (/about), рдЕрдиреНрдп рд▓реЛрдХреЗрд▓реНрд╕ рдкреНрд░реАрдлрд╝рд┐рдХреНрд╕ рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВ (/fr/about)ред

      src/i18n/config.ts
      export const locales = ["en", "fr", "es"] as const;
      
      export type Locale = (typeof locales)[number];
      
      export const defaultLocale: Locale = "en";
      
      /** Public origin, used for canonical URLs, hreflang and the sitemap. */
      export const siteUrl = "https://example.com";
      
      /** Cookie storing the locale explicitly chosen by the visitor. */
      export const localeCookieName = "locale";
      
      /** Open Graph expects `language_TERRITORY` codes. */
      export const openGraphLocales: Record<Locale, string> = {
        en: "en_US",
        fr: "fr_FR",
        es: "es_ES",
      };
      
      export const isLocale = (value: unknown): value is Locale =>
        typeof value === "string" && (locales as readonly string[]).includes(value);
      
      /** Maps the optional `{-$locale}` route param to a supported locale. */
      export const resolveLocale = (localeParam: string | undefined): Locale =>
        isLocale(localeParam) ? localeParam : defaultLocale;
      
      /** The value to pass as `locale` param: `undefined` for the default locale. */
      export const toLocaleParam = (locale: Locale): Locale | undefined =>
        locale === defaultLocale ? undefined : locale;
      
      const rightToLeftLanguages = new Set(["ar", "fa", "he", "ur", "ps", "yi"]);
      
      export const getTextDirection = (locale: string): "ltr" | "rtl" =>
        rightToLeftLanguages.has(new Intl.Locale(locale).language) ? "rtl" : "ltr";
      
      /** `localizePath("/about", "fr")` тЖТ `/fr/about`, default locale unprefixed. */
      export const localizePath = (path: string, locale: Locale): string => {
        if (locale === defaultLocale) return path;
      
        return path === "/" ? `/${locale}` : `/${locale}${path}`;
      };
      
      export const getAbsoluteUrl = (path: string, locale: Locale): string =>
        `${siteUrl}${localizePath(path, locale)}`;
      
      export const getLocaleName = (locale: Locale): string =>
        new Intl.DisplayNames([locale], { type: "language" }).of(locale) ?? locale;
      
    3. Lingui рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВ

      Lingui рдХреЙрдиреНрдлрд╝рд┐рдЧ рдЙрд╕реА рд▓реЛрдХреЗрд▓ рд╕реВрдЪреА рдХрд╛ рдкреБрди: рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдХреИрдЯрд▓реЙрдЧ, рд░рд╛рдЙрдЯрд░ рдФрд░ рд╕рд╛рдЗрдЯрдореИрдк рдХрднреА рднреА рдЕрд╕рд╣рдордд рдирд╣реАрдВ рд╣реЛрддреЗ рд╣реИрдВред

      lingui.config.ts
      import { defineConfig } from "@lingui/cli";
      import { formatter } from "@lingui/format-po";
      import { defaultLocale, locales } from "./src/i18n/config";
      
      export default defineConfig({
        sourceLocale: defaultLocale,
        locales: [...locales],
        catalogs: [
          {
            path: "<rootDir>/src/locales/{locale}/messages",
            include: ["src"],
          },
        ],
        format: formatter({ lineNumbers: false }),
      });
      

      рдирд┐рд╖реНрдХрд░реНрд╖рдг (extraction) рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЬреЛрдбрд╝реЗрдВ:

      package.json
      {
        "scripts": {
          "i18n:extract": "lingui extract --clean",
          "i18n:check": "lingui extract --clean && git diff --exit-code src/locales"
        }
      }
      

      i18n:check CI рдореЗрдВ рд╡рд┐рдлрд▓ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ рдЬрдм рдХрд┐рд╕реА рдХрдВрдкреЛрдиреЗрдВрдЯ рдореЗрдВ рдРрд╕рд╛ рд╕рдВрджреЗрд╢ рд╣реЛрддрд╛ рд╣реИ рдЬрд┐рд╕реЗ рдирд┐рдХрд╛рд▓рд╛ рдФрд░ рдХрдорд┐рдЯ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред

    4. Vite рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВ

      @vitejs/plugin-react v6 рдХреЗ рд╕рд╛рде, Babel рдЕрдм рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд рдирд╣реАрдВ рд╣реИред @rolldown/plugin-babel Lingui рдореИрдХреНрд░реЛ рдкреНрд▓рдЧрдЗрди рдЪрд▓рд╛рддрд╛ рд╣реИ, рдФрд░ linguiTransformerBabelPreset рдХреЗрд╡рд▓ рдЙрди рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рдкреНрд░реЛрд╕реЗрд╕ рдХрд░рддрд╛ рд╣реИ рдЬреЛ рдореИрдХреНрд░реЛ рдЗрдореНрдкреЛрд░реНрдЯ рдХрд░рддреА рд╣реИрдВ, рдЬреЛ рдмрд┐рд▓реНрдб рдХреЛ рддреЗрдЬрд╝ рд░рдЦрддрд╛ рд╣реИред

      vite.config.ts
      import { lingui, linguiTransformerBabelPreset } from "@lingui/vite-plugin";
      import babel from "@rolldown/plugin-babel";
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { defineConfig } from "vite";
      
      export default defineConfig({
        plugins: [
          tanstackStart(),
          viteReact(),
          lingui(),
          babel({ presets: [linguiTransformerBabelPreset()] }),
        ],
      });
      
    5. рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдХреИрдЯрд▓реЙрдЧ рд▓реЛрдб рдХрд░реЗрдВ

      import() рдореЗрдВ рдЯреЗрдореНрдкрд▓реЗрдЯ рд▓рд┐рдЯрд░рд▓ Vite рдХреЛ рдкреНрд░рддрд┐ рдХреИрдЯрд▓реЙрдЧ рдПрдХ рд╣рд┐рд╕реНрд╕рд╛ (chunk) рдЙрддреНрд╕рд░реНрдЬрд┐рдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, рдФрд░ Lingui рдкреНрд▓рдЧрдЗрди .po рдлрд╝рд╛рдЗрд▓ рдХреЛ рдЗрд╕рдореЗрдВ рд╕рдВрдХрд▓рд┐рдд рдХрд░рддрд╛ рд╣реИред рдПрдХ рдлреНрд░реЗрдВрдЪ рд╡рд┐рдЬрд╝рд┐рдЯрд░ рдХреЗрд╡рд▓ рдлреНрд░реЗрдВрдЪ рдХреИрдЯрд▓реЙрдЧ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рддрд╛ рд╣реИред

      рд╕рдВрдХрд▓рд┐рдд рд╕рдВрджреЗрд╢ рд╕рд╛рдорд╛рдиреНрдп рдбреЗрдЯрд╛ рд╣реЛрддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдЙрдиреНрд╣реЗрдВ рд░реВрдЯ рд▓реЛрдбрд░ рджреНрд╡рд╛рд░рд╛ рд▓реМрдЯрд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, HTML рдореЗрдВ рдХреНрд░рдордмрджреНрдз (serialized) рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдФрд░ рд╣рд╛рдЗрдбреНрд░реЗрд╢рди рдкрд░ рдкреБрди: рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

      src/i18n/lingui.ts
      import { type I18n, type Messages, setupI18n } from "@lingui/core";
      import type { Locale } from "./config";
      
      /**
       * Loads the compiled catalog of one locale (one chunk per locale).
       */
      export const loadCatalog = async (locale: Locale): Promise<Messages> => {
        const { messages } = await import(`../locales/${locale}/messages.po`);
      
        return messages;
      };
      
      /**
       * Creates an isolated I18n instance: safe for concurrent SSR requests.
       */
      export const createI18n = (locale: Locale, messages: Messages): I18n =>
        setupI18n({ locale, messages: { [locale]: messages } });
      
      /**
       * Loads a catalog and returns a ready-to-use instance, for loaders and
       * server functions.
       */
      export const loadI18n = async (locale: Locale): Promise<I18n> =>
        createI18n(locale, await loadCatalog(locale));
      

      TypeScript рдХреЗ рд▓рд┐рдП .po рдЖрдпрд╛рдд рдХреЛ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдореЙрдбреНрдпреВрд▓ рдХреЛ рдПрдХ рдмрд╛рд░ рдШреЛрд╖рд┐рдд рдХрд░реЗрдВ:

      src/i18n/po.d.ts
      declare module "*.po" {
        import type { Messages } from "@lingui/core";
      
        export const messages: Messages;
      }
      
    6. рд░реВрдЯ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдмрдирд╛рдПрдВ

      рд░реВрдЯ рд░реВрдЯ рд╕рд░реНрд╡рд░-рд░реЗрдВрдбрд░ рдХрд┐рдП рдЧрдП <html> рдкрд░ lang рдФрд░ dir рд╕реЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡реИрдХрд▓реНрдкрд┐рдХ рд▓реЛрдХреЗрд▓ рдкреИрд░рдо рдХреЛ рдкрдврд╝рддрд╛ рд╣реИред

      src/routes/__root.tsx
      import {
        createRootRoute,
        HeadContent,
        Scripts,
        useParams,
      } from "@tanstack/react-router";
      import type { ReactNode } from "react";
      import { getTextDirection, resolveLocale } from "@/i18n/config";
      
      export const Route = createRootRoute({
        head: () => ({
          meta: [
            { charSet: "utf-8" },
            { name: "viewport", content: "width=device-width, initial-scale=1" },
          ],
        }),
        shellComponent: RootDocument,
      });
      
      function RootDocument({ children }: { children: ReactNode }) {
        const { locale: localeParam } = useParams({ strict: false });
        const locale = resolveLocale(localeParam);
      
        return (
          <html lang={locale} dir={getTextDirection(locale)}>
            <head>
              <HeadContent />
            </head>
            <body>
              {children}
              <Scripts />
            </body>
          </html>
        );
      }
      
    7. рд▓реЛрдХреЗрд▓ рд▓реЗрдЖрдЙрдЯ рд░реВрдЯ рдмрдирд╛рдПрдВ

      {-$locale} рдлрд╝реЛрд▓реНрдбрд░ рдПрдХ рд╡реИрдХрд▓реНрдкрд┐рдХ рдкрд╛рде рд╕реЗрдЧрдореЗрдВрдЯ рдмрдирд╛рддрд╛ рд╣реИ: /about рдФрд░ /fr/about рджреЛрдиреЛрдВ /{-$locale}/about рд╕реЗ рдореЗрд▓ рдЦрд╛рддреЗ рд╣реИрдВред рд▓реЗрдЖрдЙрдЯ рдЕрдЬреНрдЮрд╛рдд рдкреНрд░реАрдлрд╝рд┐рдХреНрд╕ рдХреЛ рдЕрд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддрд╛ рд╣реИ, рд╡рд░реНрддрдорд╛рди рд▓реЛрдХреЗрд▓ рдХреЗ рдХреИрдЯрд▓реЙрдЧ рдХреЛ рд▓реЛрдб рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдПрдХ рд╕рдорд░реНрдкрд┐рдд I18n рдЗрдВрд╕реНрдЯреЗрдВрд╕ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред

      src/routes/{-$locale}/route.tsx
      import { I18nProvider } from "@lingui/react";
      import { createFileRoute, notFound, Outlet } from "@tanstack/react-router";
      import { useMemo } from "react";
      import { Header } from "@/components/Header";
      import { NotFound } from "@/components/NotFound";
      import { isLocale, resolveLocale } from "@/i18n/config";
      import { createI18n, loadCatalog } from "@/i18n/lingui";
      
      export const Route = createFileRoute("/{-$locale}")({
        beforeLoad: ({ params }) => {
          if (params.locale !== undefined && !isLocale(params.locale)) {
            throw notFound();
          }
        },
        loader: async ({ params }) => {
          const locale = resolveLocale(params.locale);
      
          return { locale, messages: await loadCatalog(locale) };
        },
        // A catalog never changes for a given locale
        staleTime: Infinity,
        component: LocaleLayout,
        notFoundComponent: NotFound,
      });
      
      function LocaleLayout() {
        const { locale, messages } = Route.useLoaderData();
      
        // One instance per locale, never shared between requests
        const i18n = useMemo(() => createI18n(locale, messages), [locale, messages]);
      
        return (
          <I18nProvider i18n={i18n}>
            <Header />
            <main>
              <Outlet />
            </main>
          </I18nProvider>
        );
      }
      
    8. рдЕрдкрдиреЗ рдкреЗрдЬреЛрдВ рдореЗрдВ рдЕрдиреБрд╡рд╛рджреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ

      рдХрдВрдкреЛрдиреЗрдВрдЯ рдореЗрдВ рд╕реНрд░реЛрдд рдЯреЗрдХреНрд╕реНрдЯ рд▓рд┐рдЦреЗрдВред рдореИрдХреНрд░реЛрдЬрд╝ рдЗрд╕реЗ рдмрд┐рд▓реНрдб рд╕рдордп рдкрд░ рдореИрд╕реЗрдЬ рдЖрдИрдбреА рдореЗрдВ рдмрджрд▓ рджреЗрддреЗ рд╣реИрдВ, рдФрд░ lingui extract рдЗрд╕реЗ рдПрдХрддреНрд░ рдХрд░рддрд╛ рд╣реИред

      • JSX рд╕рд╛рдордЧреНрд░реА рдХреЗ рд▓рд┐рдП <Trans>, рдиреЗрд╕реНрдЯреЗрдб рддрддреНрд╡реЛрдВ рд╕рд╣рд┐рдд;
      • рд╕реНрдЯреНрд░рд┐рдВрдЧреНрд╕ (рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ, рдкреНрд░реЙрдкреНрд╕) рдХреЗ рд▓рд┐рдП useLingui().t;
      • ICU рдмрд╣реБрд╡рдЪрдиреЛрдВ рдХреЗ рд▓рд┐рдП <Plural>ред
      src/routes/{-$locale}/about.tsx
      import { msg } from "@lingui/core/macro";
      import { Plural, Trans, useLingui } from "@lingui/react/macro";
      import { createFileRoute } from "@tanstack/react-router";
      import { useState } from "react";
      import { resolveLocale } from "@/i18n/config";
      import { loadI18n } from "@/i18n/lingui";
      import { buildLocalizedHead } from "@/i18n/seo";
      
      export const Route = createFileRoute("/{-$locale}/about")({
        // Translate the metadata in the loader: head() stays synchronous
        loader: async ({ params }) => {
          const i18n = await loadI18n(resolveLocale(params.locale));
      
          return {
            metadata: {
              title: i18n._(msg`About us`),
              description: i18n._(
                msg`Learn who we are and why we built this application.`
              ),
            },
          };
        },
        staleTime: Infinity,
        head: ({ params, loaderData }) =>
          loaderData
            ? buildLocalizedHead({
                path: "/about",
                locale: resolveLocale(params.locale),
                ...loaderData.metadata,
              })
            : {},
        component: AboutPage,
      });
      
      function AboutPage() {
        const { t } = useLingui();
        const [count, setCount] = useState(0);
      
        return (
          <>
            <h1>
              <Trans>About us</Trans>
            </h1>
            <p>
              <Plural
                value={count}
                _0="No clicks yet"
                one="# click"
                other="# clicks"
              />
            </p>
            <button
              type="button"
              aria-label={t`Counter`}
              onClick={() => setCount((value) => value + 1)}
            >
              <Trans>Increment</Trans>
            </button>
          </>
        );
      }
      
      рдХрд┐рд╕реА рдХреИрдЯрд▓реЙрдЧ рдХрд╛ рдбрд╛рдпрдиреЗрдорд┐рдХ import() рдореЙрдбреНрдпреВрд▓ рд╕рд┐рд╕реНрдЯрдо рджреНрд╡рд╛рд░рд╛ рдХреИрд╢ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдХрдИ рд▓реЛрдбрд░реЛрдВ рдореЗрдВ loadI18n рдХреЛ рдХреЙрд▓ рдХрд░рдиреЗ рд╕реЗ рдХреИрдЯрд▓реЙрдЧ рджреЛ рдмрд╛рд░ рдбрд╛рдЙрдирд▓реЛрдб рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИред
    9. рдЕрдкрдиреЗ рд╕рдВрджреЗрд╢ рдирд┐рдХрд╛рд▓реЗрдВ рдФрд░ рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВ

      рдирд┐рд╖реНрдХрд░реНрд╖рдг (extraction) рдЪрд▓рд╛рдПрдВред Lingui рдкреНрд░рддреНрдпреЗрдХ рд╕рдВрджреЗрд╢ рдХреЛ рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреИрдЯрд▓реЙрдЧ рдореЗрдВ рд▓рд┐рдЦрддрд╛ рд╣реИ:

      bash
      npm run i18n:extract
      

      рдлрд┐рд░ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╡рд┐рд╖реНрдЯрд┐ рдХреЗ msgstr рдХрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВ:

      src/locales/fr/messages.po
      msgid "About us"
      msgstr "├А propos"
      
      msgid "Learn who we are and why we built this application."
      msgstr "D├йcouvrez qui nous sommes et pourquoi nous avons cr├й├й cette application."
      
      msgid "Increment"
      msgstr "Incr├йmenter"
      
      msgid "Counter"
      msgstr "Compteur"
      
      msgid "{count, plural, =0 {No clicks yet} one {# click} other {# clics}}"
      msgstr "{count, plural, =0 {Aucun clic} one {# clic} other {# clics}}"
      
      src/locales/es/messages.po
      msgid "About us"
      msgstr "Sobre nosotros"
      
      msgid "Learn who we are and why we built this application."
      msgstr "Descubre qui├йnes somos y por qu├й creamos esta aplicaci├│n."
      
      msgid "Increment"
      msgstr "Incrementar"
      
      msgid "Counter"
      msgstr "Contador"
      
      msgid "{count, plural, =0 {No clicks yet} one {# click} other {# clics}}"
      msgstr "{count, plural, =0 {Ning├║n clic} one {# clic} other {# clics}}"
      
      рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ, рд╕рдВрджреЗрд╢ рдЖрдИрдбреА рд╕реНрд░реЛрдд рдЯреЗрдХреНрд╕реНрдЯ рдХреЗ рд╣реИрд╢ рд╣реЛрддреЗ рд╣реИрдВ: рдЕрдВрдЧреНрд░реЗрдЬреА рдЯреЗрдХреНрд╕реНрдЯ рдмрджрд▓рдиреЗ рд╕реЗ рдПрдХ рдирдпрд╛ рд╕рдВрджреЗрд╢ рдмрдирддрд╛ рд╣реИред рдЙрди рдЯреЗрдХреНрд╕реНрдЯ рдХреЗ рд▓рд┐рдП рд╕реНрдкрд╖реНрдЯ рдЖрдИрдбреА (<Trans id="about.title">About us</Trans>) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ рдЬреЛ рдЕрдХреНрд╕рд░ рдмрджрд▓рддреЗ рд╣реИрдВред
    10. рдПрдХ рд╕реНрдерд╛рдиреАрдпрдХреГрдд рд▓рд┐рдВрдХ рдШрдЯрдХ рдмрдирд╛рдПрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      рдкреНрд░рддреНрдпреЗрдХ рд░реВрдЯ {-$locale} рдХреЗ рдЕрдВрддрд░реНрдЧрдд рд░рд╣рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд▓рд┐рдВрдХ рдореЗрдВ рд╡рд░реНрддрдорд╛рди рд▓реЛрдХреЗрд▓ рдкреИрд░рд╛рдореАрдЯрд░ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред

      src/components/LocalizedLink.tsx
      import { useLingui } from "@lingui/react";
      import { Link, type LinkComponentProps } from "@tanstack/react-router";
      import { type Locale, toLocaleParam } from "@/i18n/config";
      
      type LocalizedLinkProps = Omit<LinkComponentProps, "params">;
      
      export const LocalizedLink = (props: LocalizedLinkProps) => {
        const { i18n } = useLingui();
      
        return (
          <Link
            {...props}
            params={{ locale: toLocaleParam(i18n.locale as Locale) }}
          />
        );
      };
      
    11. рдЕрдкрдиреА рд╕рд╛рдордЧреНрд░реА рдХреА рднрд╛рд╖рд╛ рдмрджрд▓реЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      рд╕реНрд╡рд┐рдЪрд░ рдХреЛ рд▓рд┐рдВрдХ рдХреЗ рд░реВрдк рдореЗрдВ рд░реЗрдВрдбрд░ рдХрд░реЗрдВ, рддрд╛рдХрд┐ рдХреНрд░реЙрд▓рд░реНрд╕ рдХреЛ рд╣рд░ рднрд╛рд╖рд╛ рдХрд╛ рд╕рдВрд╕реНрдХрд░рдг рдорд┐рд▓ рд╕рдХреЗред to="." рд╡рд░реНрддрдорд╛рди рдкреЗрдЬ рдХреЛ рдмрдирд╛рдП рд░рдЦрддрд╛ рд╣реИ рдФрд░ рд▓реЛрдХреЗрд▓ рдкреИрд░рд╛рдореАрдЯрд░ рдХреЛ рдмрджрд▓рддрд╛ рд╣реИред рд▓реЛрдХреЗрд▓ рд▓реЗрдЖрдЙрдЯ рдХрд╛ рд▓реЛрдбрд░ рдлрд┐рд░ рдирдпрд╛ рдХреИрдЯрд▓реЙрдЧ рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИред

      src/components/LocaleSwitcher.tsx
      import { useLingui } from "@lingui/react/macro";
      import { Link } from "@tanstack/react-router";
      import {
        getLocaleName,
        type Locale,
        localeCookieName,
        locales,
        toLocaleParam,
      } from "@/i18n/config";
      
      const persistLocale = (locale: Locale) => {
        document.cookie = `${localeCookieName}=${locale}; Path=/; Max-Age=31536000; SameSite=Lax`;
      };
      
      export const LocaleSwitcher = () => {
        // The macro version also returns the i18n instance
        const { i18n, t } = useLingui();
      
        return (
          <nav aria-label={t`Change language`}>
            <ul>
              {locales.map((locale) => (
                <li key={locale}>
                  <Link
                    to="."
                    params={(previous) => ({
                      ...previous,
                      locale: toLocaleParam(locale),
                    })}
                    hrefLang={locale}
                    lang={locale}
                    aria-current={locale === i18n.locale ? "page" : undefined}
                    onClick={() => persistLocale(locale)}
                  >
                    {getLocaleName(locale)}
                  </Link>
                </li>
              ))}
            </ul>
          </nav>
        );
      };
      
    12. рдЕрдкрдиреЗ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХрд╛ рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдХрд░реЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      рдкреНрд░рддреНрдпреЗрдХ рднрд╛рд╖рд╛ рд╕рдВрд╕реНрдХрд░рдг рдЕрдкрдиреЗ рджрдо рдкрд░ рд░реИрдВрдХ рдХрд░ рд╕рдХрддрд╛ рд╣реИ, рдмрд╢рд░реНрддреЗ рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдПрдХ рдЕрдиреБрд╡рд╛рджрд┐рдд <title> рдФрд░ рд╡рд┐рд╡рд░рдг, рдПрдХ рд╕реНрд╡-рд╕рдВрджрд░реНрднрд┐рдд рдХреИрдиреЛрдирд┐рдХрд▓, рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ hreflang рдкреНрд▓рд╕ x-default, Open Graph рд▓реЛрдХреЗрд▓реНрд╕ рдФрд░ inLanguage рдХреЗ рд╕рд╛рде JSON-LD рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░реЗред рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХрд╛ рдЕрдиреБрд╡рд╛рдж рд▓реЛрдбрд░ рдореЗрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ (рдЪрд░рдг 8), рдФрд░ рдпрд╣ рд╣реЗрд▓реНрдкрд░ рдмрд╛рдХреА рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░рддрд╛ рд╣реИ:

      src/i18n/seo.ts
      import {
        defaultLocale,
        getAbsoluteUrl,
        type Locale,
        locales,
        openGraphLocales,
      } from "./config";
      
      type LocalizedHeadOptions = {
        /** Path without locale prefix, e.g. "/about" */
        path: string;
        locale: Locale;
        title: string;
        description: string;
      };
      
      export const buildLocalizedHead = ({
        path,
        locale,
        title,
        description,
      }: LocalizedHeadOptions) => {
        const url = getAbsoluteUrl(path, locale);
      
        return {
          meta: [
            { title },
            { name: "description", content: description },
            { property: "og:type", content: "website" },
            { property: "og:title", content: title },
            { property: "og:description", content: description },
            { property: "og:url", content: url },
            { property: "og:locale", content: openGraphLocales[locale] },
            ...locales
              .filter((alternateLocale) => alternateLocale !== locale)
              .map((alternateLocale) => ({
                property: "og:locale:alternate",
                content: openGraphLocales[alternateLocale],
              })),
          ],
          links: [
            { rel: "canonical", href: url },
            ...locales.map((alternateLocale) => ({
              rel: "alternate",
              hrefLang: alternateLocale,
              href: getAbsoluteUrl(path, alternateLocale),
            })),
            {
              rel: "alternate",
              hrefLang: "x-default",
              href: getAbsoluteUrl(path, defaultLocale),
            },
          ],
          scripts: [
            {
              type: "application/ld+json",
              children: JSON.stringify({
                "@context": "https://schema.org",
                "@type": "WebPage",
                name: title,
                description,
                url,
                inLanguage: locale,
              }),
            },
          ],
        };
      };
      
    13. рдЕрдкрдиреЗ рд╕рд╛рдЗрдЯрдореИрдк рдФрд░ robots.txt рдХрд╛ рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдХрд░реЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      рд╕рд╛рдЗрдЯрдореИрдк рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреЗ рдкреНрд░рддреНрдпреЗрдХ URL рдХреЛ рд╕реВрдЪреАрдмрджреНрдз рдХрд░рддрд╛ рд╣реИ, рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╡рд┐рд╖реНрдЯрд┐ xhtml:link рдХреЗ рд╕рд╛рде рдЕрдкрдиреЗ рд╕рднреА рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреЛ рдШреЛрд╖рд┐рдд рдХрд░рддреА рд╣реИред robots.txt рд╣рд░ рднрд╛рд╖рд╛ рдореЗрдВ рдирд┐рдЬреА рдорд╛рд░реНрдЧреЛрдВ рдХреЛ рд░реЛрдХрддрд╛ рд╣реИ рдФрд░ рд╕рд╛рдЗрдЯрдореИрдк рдХреА рдУрд░ рдЗрдВрдЧрд┐рдд рдХрд░рддрд╛ рд╣реИред рдпрджрд┐ рд╕реНрдЯрд╛рд░реНрдЯрд░ рдиреЗ рдПрдХ рдмрдирд╛рдпрд╛ рд╣реИ рддреЛ public/robots.txt рдХреЛ рд╣рдЯрд╛ рджреЗрдВред

      src/routes/sitemap[.]xml.ts
      import { createFileRoute } from "@tanstack/react-router";
      import { defaultLocale, getAbsoluteUrl, locales } from "@/i18n/config";
      
      type SitemapPage = {
        path: string;
        changeFrequency: "daily" | "weekly" | "monthly";
        priority: number;
      };
      
      export const sitemapPages: SitemapPage[] = [
        { path: "/", changeFrequency: "daily", priority: 1.0 },
        { path: "/about", changeFrequency: "monthly", priority: 0.8 },
      ];
      
      const buildAlternateLinks = (path: string): string =>
        [
          ...locales.map(
            (locale) =>
              `<xhtml:link rel="alternate" hreflang="${locale}" href="${getAbsoluteUrl(path, locale)}"/>`
          ),
          `<xhtml:link rel="alternate" hreflang="x-default" href="${getAbsoluteUrl(path, defaultLocale)}"/>`,
        ].join("");
      
      const buildSitemap = (): string => {
        const urls = sitemapPages.flatMap((page) =>
          locales.map(
            (locale) =>
              `<url><loc>${getAbsoluteUrl(page.path, locale)}</loc>${buildAlternateLinks(page.path)}<changefreq>${page.changeFrequency}</changefreq><priority>${page.priority}</priority></url>`
          )
        );
      
        return `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">${urls.join("")}</urlset>`;
      };
      
      export const Route = createFileRoute("/sitemap.xml")({
        server: {
          handlers: {
            GET: () =>
              new Response(buildSitemap(), {
                headers: { "Content-Type": "application/xml; charset=utf-8" },
              }),
          },
        },
      });
      
      src/routes/robots[.]txt.ts
      import { createFileRoute } from "@tanstack/react-router";
      import { locales, localizePath, siteUrl } from "@/i18n/config";
      
      const privatePaths = ["/dashboard", "/admin"];
      
      const buildRobots = (): string =>
        [
          "User-agent: *",
          "Allow: /",
          ...privatePaths.flatMap((path) =>
            locales.map((locale) => `Disallow: ${localizePath(path, locale)}`)
          ),
          "",
          `Sitemap: ${siteUrl}/sitemap.xml`,
        ].join("\n");
      
      export const Route = createFileRoute("/robots.txt")({
        server: {
          handlers: {
            GET: () =>
              new Response(buildRobots(), {
                headers: { "Content-Type": "text/plain; charset=utf-8" },
              }),
          },
        },
      });
      
    14. рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреЛ рдкреНрд░реА-рд░реЗрдВрдбрд░ рдХрд░реЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      рдкреНрд░рддреНрдпреЗрдХ рд╕реНрдерд╛рдиреАрдпрдХреГрдд рдкрде рдХреЛ рд╕реВрдЪреАрдмрджреНрдз рдХрд░реЗрдВ рддрд╛рдХрд┐ TanStack Start рдмрд┐рд▓реНрдб рд╕рдордп рдкрд░ рд╕рднреА рднрд╛рд╖рд╛ рд╕рдВрд╕реНрдХрд░рдгреЛрдВ рдХреЛ рдкреНрд░реА-рд░реЗрдВрдбрд░ рдХрд░ рд╕рдХреЗ:

      vite.config.ts
      import { lingui, linguiTransformerBabelPreset } from "@lingui/vite-plugin";
      import babel from "@rolldown/plugin-babel";
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { defineConfig } from "vite";
      import { locales, localizePath } from "./src/i18n/config";
      
      const pagePaths = ["/", "/about"];
      
      const localizedPages = pagePaths.flatMap((path) =>
        locales.map((locale) => ({
          path: localizePath(path, locale),
          prerender: { enabled: true },
        }))
      );
      
      export default defineConfig({
        plugins: [
          tanstackStart({
            prerender: { enabled: true, crawlLinks: true },
            pages: [
              ...localizedPages,
              { path: "/sitemap.xml", prerender: { enabled: true } },
              { path: "/robots.txt", prerender: { enabled: true } },
            ],
          }),
          viteReact(),
          lingui(),
          babel({ presets: [linguiTransformerBabelPreset()] }),
        ],
      });
      
    15. рдкрд╣рд▓реА рдмрд╛рд░ рдЖрдиреЗ рд╡рд╛рд▓реЗ рдЖрдЧрдВрддреБрдХреЛрдВ рдХреЛ рдкреБрдирд░реНрдирд┐рд░реНрджреЗрд╢рд┐рдд рдХрд░реЗрдВ рдФрд░ 404 рдкреЗрдЬреЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓реЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      рдПрдХ рдЕрдиреБрд░реЛрдз рдорд┐рдбрд▓рд╡реЗрдпрд░ / рдкрд░ рдЖрдиреЗ рд╡рд╛рд▓реЗ рдЖрдЧрдВрддреБрдХ рдХреЛ рдЙрдирдХреА рдкрд╕рдВрджреАрджрд╛ рднрд╛рд╖рд╛ рдореЗрдВ рднреЗрдЬрддрд╛ рд╣реИ (рдкрд╣рд▓реЗ рдХреБрдХреА, рдлрд┐рд░ Accept-Language)ред рдбреАрдк рд▓рд┐рдВрдХ рдХрднреА рднреА рдкреБрдирд░реНрдирд┐рд░реНрджреЗрд╢рд┐рдд рдирд╣реАрдВ рд╣реЛрддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдХреНрд░реЙрд▓рд░реНрд╕ рдФрд░ рд╕рд╛рдЭрд╛ рдХрд┐рдП рдЧрдП URL рд╣рдореЗрд╢рд╛ рд╡рд╣ рдкреЗрдЬ рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ рдЬрд┐рд╕рдХреЗ рд▓рд┐рдП рдЙрдиреНрд╣реЛрдВрдиреЗ рдЕрдиреБрд░реЛрдз рдХрд┐рдпрд╛ рдерд╛ред

      src/i18n/negotiateLocale.ts
      import { isLocale, type Locale } from "./config";
      
      /** "fr-CA,fr;q=0.9,en;q=0.8" тЖТ "fr" */
      export const negotiateLocale = (
        acceptLanguage: string | null | undefined
      ): Locale | undefined => {
        if (!acceptLanguage) return undefined;
      
        return acceptLanguage
          .split(",")
          .map((part) => {
            const [tag = "", quality] = part.trim().split(";q=");
      
            return {
              language: tag.toLowerCase().split("-")[0],
              quality: quality ? Number(quality) : 1,
            };
          })
          .sort((first, second) => second.quality - first.quality)
          .map(({ language }) => language)
          .find(isLocale);
      };
      
      src/start.ts
      import { redirect } from "@tanstack/react-router";
      import { createMiddleware, createStart } from "@tanstack/react-start";
      import { getCookie } from "@tanstack/react-start/server";
      import { defaultLocale, isLocale, localeCookieName } from "@/i18n/config";
      import { negotiateLocale } from "@/i18n/negotiateLocale";
      
      const localeRedirectMiddleware = createMiddleware().server(
        ({ request, next }) => {
          if (new URL(request.url).pathname !== "/") return next();
      
          const cookieLocale = getCookie(localeCookieName);
          const preferredLocale = isLocale(cookieLocale)
            ? cookieLocale
            : negotiateLocale(request.headers.get("accept-language"));
      
          if (preferredLocale && preferredLocale !== defaultLocale) {
            throw redirect({ href: `/${preferredLocale}`, statusCode: 307 });
          }
      
          return next();
        }
      );
      
      export const startInstance = createStart(() => ({
        requestMiddleware: [localeRedirectMiddleware],
      }));
      

      404 рдкреЗрдЬреЛрдВ рдХреЗ рд▓рд┐рдП, рдПрдХ рдХреИрдЪ-рдСрд▓ рд░реВрдЯ рд▓реЗрдЖрдЙрдЯ рдХреЗ рд╕реНрдерд╛рдиреАрдпрдХреГрдд notFoundComponent рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рддрд╛ рд╣реИред рдЗрд╕реЗ noindex рдХреЗ рд░реВрдк рдореЗрдВ рдЪрд┐рд╣реНрдирд┐рдд рдХрд░реЗрдВ: React 19 <meta> рдХреЛ <head> рдореЗрдВ рд╣реЛрд╕реНрдЯ рдХрд░рддрд╛ рд╣реИред

      src/components/NotFound.tsx
      import { Trans } from "@lingui/react/macro";
      import { LocalizedLink } from "./LocalizedLink";
      
      export const NotFound = () => (
        <div>
          <meta name="robots" content="noindex" />
          <h1>
            <Trans>Page not found</Trans>
          </h1>
          <LocalizedLink to="/{-$locale}">
            <Trans>Back to home</Trans>
          </LocalizedLink>
        </div>
      );
      
      src/routes/{-$locale}/$.tsx
      import { createFileRoute, notFound } from "@tanstack/react-router";
      
      export const Route = createFileRoute("/{-$locale}/$")({
        beforeLoad: () => {
          throw notFound();
        },
      });
      
    16. рдЕрдкрдиреЗ рдореИрдХреНрд░реЛрдЬрд╝ рд░рдЦреЗрдВ, Intlayer рдХреЗ рд╕рд╛рде рд░рдирдЯрд╛рдЗрдо рдХрдо рдХрд░реЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      @intlayer/lingui рд╕рдВрдЧрддрддрд╛ рдПрдбреЗрдкреНрдЯрд░ рдЖрдкрдХреЗ рд╕реНрд░реЛрдд рдХреЛ рдЕрдЫреВрддрд╛ рд░рдЦрддрд╛ рд╣реИ: рдореИрдХреНрд░реЛрдЬрд╝ рдкрд╣рд▓реЗ рдХреА рддрд░рд╣ рдмрд┐рд▓реНрдХреБрд▓ рд╕рдВрдХрд▓рд┐рдд рд╣реЛрддреЗ рд╣реИрдВ, рдФрд░ рдкрд░рд┐рдгрд╛рдореА i18n._(), useLingui() рдФрд░ <Trans> рдХреЙрд▓ рд╕рдВрдХрд▓рд┐рдд Intlayer рд╢рдмреНрджрдХреЛрд╢реЛрдВ рджреНрд╡рд╛рд░рд╛ рдкрд░реЛрд╕реЗ рдЬрд╛рддреЗ рд╣реИрдВред рдмреЗрдВрдЪрдорд╛рд░реНрдХ рдореЗрдВ, рд░рдирдЯрд╛рдЗрдо ~56.7 KB рд╕реЗ рдШрдЯрдХрд░ ~9.8 KB gzip рд╣реЛ рдЬрд╛рддрд╛ рд╣реИред

      bash
      npm install @intlayer/lingui intlayer @intlayer/sync-json-plugin
      npx intlayer init
      

      рдореИрдХреНрд░реЛ рдЯреНрд░рд╛рдВрд╕рдлрд╝реЙрд░реНрдо рдХреЗ рдмрд╛рдж рдкреНрд▓рдЧрдЗрди рдЬреЛрдбрд╝реЗрдВ, рддрд╛рдХрд┐ рдпрд╣ @lingui/core рдФрд░ @lingui/react рдХреЛ рдПрдбреЗрдкреНрдЯрд░ рдкрд░ рдЙрдкрдирд╛рдо (alias) рджреЗ рд╕рдХреЗ:

      vite.config.ts
      import { lingui as linguiIntlayer } from "@intlayer/lingui/plugin";
      import { linguiTransformerBabelPreset } from "@lingui/vite-plugin";
      import babel from "@rolldown/plugin-babel";
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { defineConfig } from "vite";
      
      export default defineConfig({
        plugins: [
          tanstackStart(),
          viteReact(),
          babel({ presets: [linguiTransformerBabelPreset()] }),
          linguiIntlayer(),
        ],
      });
      

      рдХреИрдЯрд▓реЙрдЧ рдХреЛ sync JSON рдкреНрд▓рдЧрдЗрди (JSON рдХреИрдЯрд▓реЙрдЧ) рдпрд╛ sync PO рдкреНрд▓рдЧрдЗрди (PO рдХреИрдЯрд▓реЙрдЧ) рдХреЗ рд╕рд╛рде рд╕рд┐рдВрдХреНрд░рдирд╛рдЗрдЬрд╝ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдкреВрд░рд╛ рд╕реЗрдЯрдЕрдк Lingui рд╕рдВрдЧрддрддрд╛ рдЧрд╛рдЗрдб рдореЗрдВ рджреЗрдЦреЗрдВ, рдФрд░ Lingui рдмрдирд╛рдо @intlayer/lingui рдореЗрдВ рдЖрдордиреЗ-рд╕рд╛рдордиреЗ рддреБрд▓рдирд╛ рджреЗрдЦреЗрдВред

    17. Intlayer рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдкрдиреЗ рдЕрдиреБрд╡рд╛рджреЛрдВ рдХреЛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдХрд░реЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      Lingui рд╕рдВрджреЗрд╢ рдирд┐рдХрд╛рд▓рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рджрд░реНрдЬрдиреЛрдВ рдХреИрдЯрд▓реЙрдЧ рдХреЛ рд╣рд╛рде рд╕реЗ рднрд░рдиреЗ рдореЗрдВ рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рд╕рдордп рдЬрд╛рддрд╛ рд╣реИред Intlayer рдореБрдлрд╝реНрдд рдФрд░ рдУрдкрди рд╕реЛрд░реНрд╕ рд╣реИ, рдФрд░ рдЗрд╕рдХреЗ рдЯреВрд▓реНрд╕ Lingui рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВ:

      • AI рд╕реЗ рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВ рдЕрдкрдиреА рд╕реНрд╡рдпрдВ рдХреА API рдХреБрдВрдЬреА рдФрд░ рдкреНрд░рджрд╛рддрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗред auto fill рдФрд░ CLI рджреЗрдЦреЗрдВред
      • рдЕрдкрдиреА PO рдлрд╝рд╛рдЗрд▓реЗрдВ рдмрдирд╛рдП рд░рдЦреЗрдВ рд╕рддреНрдп рдХреЗ рд╕реНрд░реЛрдд рдХреЗ рд░реВрдк рдореЗрдВ sync PO рдкреНрд▓рдЧрдЗрди рдХреЗ рд╕рд╛рдеред
      • рд▓рд╛рдкрддрд╛ рдЕрдиреБрд╡рд╛рджреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░реЗрдВ CI рдореЗрдВред рдЕрдкрдиреЗ рдЕрдиреБрд╡рд╛рджреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдирд╛ рджреЗрдЦреЗрдВред
      • рдЕрдкрдиреА рдкрд░рд┐рдирд┐рдпреЛрдЬрд┐рдд (deployed) рд╕рд╛рдЗрдЯ рдХрд╛ рдСрдбрд┐рдЯ рдХрд░реЗрдВ рд▓рд╛рдкрддрд╛ hreflang, рдЧрд▓рдд рдХреИрдиреЛрдирд┐рдХрд▓ рдФрд░ рд▓реЛрдХреЗрд▓ рд▓реАрдХ рдХреЗ рд▓рд┐рдП scan рдХрдорд╛рдВрдб рдХреЗ рд╕рд╛рдеред

    рдЕрдХреНрд╕рд░ рдкреВрдЫреЗ рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ рдкреНрд░рд╢реНрди

    рд╣рд╛рдБред Lingui рдХрд╛ рдХреЛрдИ рд╕рдорд░реНрдкрд┐рдд TanStack Start рдПрдХреАрдХрд░рдг рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕рдХрд╛ Vite рдкреНрд▓рдЧрдЗрди рдФрд░ Babel рдореИрдХреНрд░реЛ рдкреНрд▓рдЧрдЗрди рдЬреНрдпреЛрдВ рдХреЗ рддреНрдпреЛрдВ рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВред рд╕рд╣реА рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рджреЛ рдмрд┐рдВрджреБ рд╣реИрдВ @rolldown/plugin-babel рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдореИрдХреНрд░реЛрдЬрд╝ рдЪрд▓рд╛рдирд╛ (Vite 8 рдФрд░ @vitejs/plugin-react v6 рдореЗрдВ рдЕрдм Babel рд╢рд╛рдорд┐рд▓ рдирд╣реАрдВ рд╣реИ), рдФрд░ SSR рдХреЗ рджреМрд░рд╛рди рд╡реИрд╢реНрд╡рд┐рдХ рдЗрдВрд╕реНрдЯреЗрдВрд╕ рдХреЛ рд╕рдХреНрд░рд┐рдп рдХрд░рдиреЗ рдХреЗ рдмрдЬрд╛рдп рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ I18n рдЗрдВрд╕реНрдЯреЗрдВрд╕ рдмрдирд╛рдирд╛ред

    рд╕рд░реНрд╡рд░ рдкрд░, рдПрдХ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдПрдХ рд╣реА рд╕рдордп рдореЗрдВ рдХрдИ рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рддреА рд╣реИред рдПрдХ рд╕рд╛рдЭрд╛ рдСрдмреНрдЬреЗрдХреНрдЯ рдкрд░ i18n.activate("fr") рдХреЛ рдХреЙрд▓ рдХрд░рдиреЗ рд╕реЗ рд╕рдорд╛рдирд╛рдВрддрд░ рдореЗрдВ рдЕрдВрдЧреНрд░реЗрдЬреА рдореЗрдВ рд░реЗрдВрдбрд░ рд╣реЛрдиреЗ рд╡рд╛рд▓реЗ рдЕрдиреБрд░реЛрдз рдХреА рднрд╛рд╖рд╛ рдмрджрд▓ рдЬрд╛рдПрдЧреАред setupI18n рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ рдЕрд▓рдЧ рдЗрдВрд╕реНрдЯреЗрдВрд╕ рдмрдирд╛рддрд╛ рд╣реИ, рдЬреЛ рд╕реБрд░рдХреНрд╖рд┐рдд рд╣реИред

    рдирд╣реАрдВред @lingui/vite-plugin рдЖрдпрд╛рдд рдХрд┐рдП рдЬрд╛рдиреЗ рдкрд░ .po рдХреИрдЯрд▓реЙрдЧ рдХреЛ рд╕рдВрдХрд▓рд┐рдд рдХрд░рддрд╛ рд╣реИред рдЖрдк рдХреЗрд╡рд▓ рдирдП рд╕рдВрджреЗрд╢ рдПрдХрддреНрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП lingui extract рдЪрд▓рд╛рддреЗ рд╣реИрдВред

    рдЙрдиреНрд╣реЗрдВ msg рдореИрдХреНрд░реЛ рдХреЗ рд╕рд╛рде рдШреЛрд╖рд┐рдд рдХрд░реЗрдВ, рдФрд░ рд░реВрдЯ рд▓реЛрдбрд░ рдореЗрдВ i18n._(msg`...`) рдХреЗ рд╕рд╛рде рдЙрдирдХрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВред рд▓реЛрдбрд░ рд╕рд╛рджреЗ рд╕реНрдЯреНрд░рд┐рдВрдЧреНрд╕ рд▓реМрдЯрд╛рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП head() рд╕рд┐рдВрдХреНрд░реЛрдирд╕ рд░рд╣рддрд╛ рд╣реИ рдФрд░ рдорд╛рди рд╣рд╛рдЗрдбреНрд░реЗрд╢рди рдХреЗ рд▓рд┐рдП рдХреНрд░рдордмрджреНрдз рд╣реЛрддреЗ рд╣реИрдВред рдЪрд░рдг 8 рдФрд░ рдЪрд░рдг 12 рдкреВрд░рд╛ рд╕реЗрдЯрдЕрдк рджрд┐рдЦрд╛рддреЗ рд╣реИрдВред

    рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд░рдирдЯрд╛рдЗрдо рдХреЗ рд▓рд┐рдП ~56.7 KB gzip рдорд╛рдкрддрд╛ рд╣реИред рдорд╛рдВрдЧ рдкрд░ рд▓реЛрдб рдХрд┐рдП рдЧрдП рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ рдХреИрдЯрд▓реЙрдЧ рдХреЗ рд╕рд╛рде, рдкреЗрдЬреЛрдВ рдХрд╛ рд╡рдЬрди рдмрд┐рдирд╛ i18n рдХреЗ 111 KB рдХреЗ рдореБрдХрд╛рдмрд▓реЗ ~115 KB рд╣реЛрддрд╛ рд╣реИред рд╣рд░ рдХреИрдЯрд▓реЙрдЧ рдХреЛ рд╕реНрдерд┐рд░ рд░реВрдк рд╕реЗ рдЖрдпрд╛рдд рдХрд░рдиреЗ рд╕реЗ рдпрд╣ рдмрдврд╝рдХрд░ ~152 KB рд╣реЛ рдЬрд╛рддрд╛ рд╣реИред

    рд╣рд╛рдБред @intlayer/lingui рдПрдбреЗрдкреНрдЯрд░ рдореИрдХреНрд░реЛрдЬрд╝ рдХреЛ рд░рдЦрддрд╛ рд╣реИ рдФрд░ рд░рдирдЯрд╛рдЗрдо рдХреЛ рд╕реНрд╡реИрдк рдХрд░рддрд╛ рд╣реИред рдлрд┐рд░ рдЖрдк рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдХреЛ рдПрдХ-рдПрдХ рдХрд░рдХреЗ useIntlayer рдореЗрдВ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рд╕рдВрдЧрддрддрд╛ рдПрдбреЗрдкреНрдЯрд░ рджреЗрдЦреЗрдВред

    рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

    рдЕрднреА рддрдХ рдХреЛрдИ рдЯрд┐рдкреНрдкрдгреА рдирд╣реАрдВред рдЕрдкрдиреЗ рд╡рд┐рдЪрд╛рд░ рд╕рд╛рдЭрд╛ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдкрд╣рд▓реЗ рд╡реНрдпрдХреНрддрд┐ рдмрдиреЗрдВред

    рд╕рдВрдмрдВрдзрд┐рдд рдкреЛрд╕реНрдЯ

    рдирд╡реАрдирддрдо рдкреЛрд╕реНрдЯ

    рдХреНрдпрд╛ Intlayer, Paraglide рд╕реЗ рд╣рд▓реНрдХрд╛ рд╣реИ?

    Paraglide i18n рдмреЗрдВрдЪрдорд╛рд░реНрдХ рдореЗрдВ рд▓рдЧрднрдЧ рд╢реВрдиреНрдп рднрд╛рд░ рд╡рд╛рд▓рд╛ рдкреНрд░рддреАрдд рд╣реЛрддрд╛ рд╣реИ рдХреНрдпреЛрдВрдХрд┐ рдЗрд╕рдХрд╛ рдХреЛрдб рд╕реАрдзреЗ рдЖрдкрдХреЗ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдЬреЗрдирд░реЗрдЯ рд╣реЛрддрд╛ рд╣реИред рдЬрд╛рдирд┐рдП рдпрд╣ рд╡рдЬрди рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХрд╣рд╛рдБ рдЬрд╛рддрд╛ рд╣реИ, рдкреНрд░рддрд┐-рдиреЛрдб рд▓реЛрдХреЗрд▓ рдкрдврд╝рдирд╛ рдкреНрд░рджрд░реНрд╢рди рдХреЛ рдХреИрд╕реЗ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░рддрд╛ рд╣реИ, рдФрд░ Intlayer рдХрд╛ рдбрд╛рдпрдирд╛рдорд┐рдХ рд▓реЛрдбрд┐рдВрдЧ рд╕рднреА рдХреЗ рдмрдЬрд╛рдп рдХреЗрд╡рд▓ рдПрдХ рд▓реЛрдХреЗрд▓ рдХреИрд╕реЗ рд▓реЛрдб рдХрд░рддрд╛ рд╣реИред

    Aymeric PINEAU