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

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

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

    use-intl рдХреНрдпрд╛ рд╣реИ?

    use-intl next-intl рдХрд╛ рдлреНрд░реЗрдорд╡рд░реНрдХ-рдПрдЧреНрдиреЙрд╕реНрдЯрд┐рдХ рдХреЛрд░ рд╣реИред рдпрд╣ Next.js рдкрд░ рдХрд┐рд╕реА рднреА рдирд┐рд░реНрднрд░рддрд╛ рдХреЗ рдмрд┐рдирд╛ рд╕рдорд╛рди useTranslations, useFormatter рдФрд░ IntlProvider APIs, ICU MessageFormat рд╕рдорд░реНрдерди рдФрд░ рдордЬрдмреВрдд TypeScript рдПрдХреАрдХрд░рдг рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред рдпрд╣ рдЗрд╕реЗ TanStack Start рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдмрд╕реЗ рдЖрдо рдЪреЙрдЗрд╕ рдореЗрдВ рд╕реЗ рдПрдХ рдмрдирд╛рддрд╛ рд╣реИ, рдФрд░ рдпрд╣ рд╡рд╣реА рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╣реИ рдЬрд┐рд╕реЗ AI рдЕрд╕рд┐рд╕реНрдЯреЗрдВрдЯ рдЗрд╕ рд╕реНрдЯреИрдХ рдХреЗ рд▓рд┐рдП рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рдмрд╛рд░ рд╕реБрдЭрд╛рддреЗ рд╣реИрдВред

    TanStack Start рдореЗрдВ рдХреЛрдИ рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд i18n рд▓реЗрдпрд░ рдирд╣реАрдВ рдЖрддреА рд╣реИред рд░реВрдЯрд┐рдВрдЧ, рд▓реЛрдХреЗрд▓ рдбрд┐рдЯреЗрдХреНрд╢рди, SEO рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдФрд░ рд╕рд╛рдЗрдЯрдореИрдк рдЬрдирд░реЗрд╢рди рдЖрдкрдХреЗ рдКрдкрд░ рдЫреЛрдбрд╝ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдпрд╣ рдЧрд╛рдЗрдб рдЗрд╕ рд╕рдм рдХреЛ рд╢реБрд░реВ рд╕реЗ рдЕрдВрдд рддрдХ рдХрд╡рд░ рдХрд░рддреА рд╣реИ:

    • рдПрдХ рд╡реИрдХрд▓реНрдкрд┐рдХ {-$locale} рд╕реЗрдЧрдореЗрдВрдЯ (/about, /fr/about) рдХреЗ рд╕рд╛рде рд▓реЛрдХреЗрд▓-рдЕрд╡реЗрдпрд░ рд░реВрдЯрд┐рдВрдЧред
    • рд░реВрдЯ-рд╡рд╛рд░ рдореИрд╕реЗрдЬ рд▓реЛрдбрд┐рдВрдЧ рддрд╛рдХрд┐ рдПрдХ рдкреЗрдЬ рдХреЗрд╡рд▓ рдЙрдиреНрд╣реАрдВ рдиреЗрдорд╕реНрдкреЗрд╕ рдФрд░ рд▓реЛрдХреЗрд▓ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗ рдЬрд┐рд╕реЗ рд╡рд╣ рд░реЗрдВрдбрд░ рдХрд░рддрд╛ рд╣реИред
    • рдмрд┐рдирд╛ рдЯреЗрдХреНрд╕реНрдЯ рдорд┐рд╕рдореИрдЪ рдХреЗ рд╕рд░реНрд╡рд░ рд░реЗрдВрдбрд░рд┐рдВрдЧ рдФрд░ рд╣рд╛рдЗрдбреНрд░реЗрд╢рдиред
    • рд╕рдВрдкреВрд░реНрдг рдмрд╣реБрднрд╛рд╖реА SEO: рдЕрдиреБрд╡рд╛рджрд┐рдд <title> рдФрд░ рд╡рд┐рд╡рд░рдг, рдХреИрдиреЛрдирд┐рдХрд▓ URL, x-default рдХреЗ рд╕рд╛рде hreflang рдСрд▓реНрдЯрд░рдиреЗрдЯреНрд╕, Open Graph рд▓реЛрдХреЗрд▓реНрд╕, JSON-LD, xhtml:link рдСрд▓реНрдЯрд░рдиреЗрдЯреНрд╕ рдХреЗ рд╕рд╛рде рд╕рд╛рдЗрдЯрдореИрдк, robots.txt рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХрд╛ рдкреНрд░реА-рд░реЗрдВрдбрд░рд┐рдВрдЧред
    рдХрд┐рд╕реА рдЕрдиреНрдп рд╕реНрдЯреИрдХ рдХреА рддрд▓рд╛рд╢ рд╣реИ? TanStack Start + Paraglide рдЧрд╛рдЗрдб, TanStack Start + Lingui рдЧрд╛рдЗрдб, рдпрд╛ TanStack Start + Intlayer рдЧрд╛рдЗрдб рджреЗрдЦреЗрдВред
    рдХреНрдпрд╛ рдЖрдк Next.js рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ? next-intl рдЧрд╛рдЗрдб рджреЗрдЦреЗрдВред

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

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

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

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

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

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

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

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

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

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

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

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

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

    рд╕реЗрдЯрдЕрдкрд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╕рд╛рдЗрдЬрд╝рдкреНрд░рддрд┐ рдкреЗрдЬ JSрдЕрдиреНрдп-рд▓реЛрдХреЗрд▓ рд▓реАрдХрдЕрдиреНрдп-рдкреЗрдЬ рд▓реАрдХ
    рдХреЛрдИ i18n рдирд╣реАрдВ (рдмреЗрд╕ рдРрдк)-111.0 KB0%0%
    use-intl (рдЗрд╕ рдЧрд╛рдЗрдб рдХрд╛ рд╕реЗрдЯрдЕрдк)75.9 KB128.7 KB0%0%
    @intlayer/use-intl (рдХрдореНрдкреИрдЯ)6.7 KB129.4 KB0%0%
    react-intlayer (рдореВрд▓ Intlayer)4.5 KB126.8 KB0%0%

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

    • рдореИрд╕реЗрдЬреЗрд╕ рдХреЛ рдкреЗрдЬ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рд╡рд┐рднрд╛рдЬрд┐рдд рдХрд░реЗрдВ рдФрд░ рдЙрдиреНрд╣реЗрдВ рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рд▓реЛрдб рдХрд░реЗрдВред рдпрд╣ рджреЛрдиреЛрдВ рд▓реАрдХ рдХреЛ рд╣рдЯрд╛ рджреЗрддрд╛ рд╣реИ, рдФрд░ рдиреАрдЪреЗ рджрд┐рдП рдЧрдП рд╕реНрдЯреЗрдкреНрд╕ рдпрд╣реА рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВред
    • рд░рдирдЯрд╛рдЗрдо рд╕реНрд╡рдпрдВ рднрд╛рд░реА рд░рд╣рддрд╛ рд╣реИ (~76 KB gzip), рдХреНрдпреЛрдВрдХрд┐ ICU рдкрд╛рд░реНрд╕рд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рднреЗрдЬрд╛ рдЬрд╛рддрд╛ рд╣реИред @intlayer/use-intl рдХрдореНрдкреИрдЯ рдПрдбреЗрдкреНрдЯрд░ (рд╕реНрдЯреЗрдк 17) ~7 KB рд░рдирдЯрд╛рдЗрдо рдХреЗ рд╕рд╛рде рдмрд┐рд▓реНрдХреБрд▓ рд╕рдорд╛рди API рдмрдирд╛рдП рд░рдЦрддрд╛ рд╣реИред
    рдкреВрд░рд╛ рдбреЗрдЯрд╛ рджреЗрдЦреЗрдВ: TanStack Start рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд░рд┐рдкреЛрд░реНрдЯ, рдФрд░ рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реАред

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

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

    рдлрд╝реАрдЪрд░react-intlayer (Intlayer)use-intlParaglide JSLingui
    рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдХреЗ рдкрд╛рд╕ рдЕрдиреБрд╡рд╛рджтЬЕ рд╕рд╣-рд╕реНрдерд┐рдд (Co-located)тЭМ рдХреЗрдВрджреНрд░реАрдХреГрдд JSONтЭМ рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ JSON рдлрд╝рд╛рдЗрд▓тЪая╕П рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдореЗрдВ рд╕реЛрд░реНрд╕ рдЯреЗрдХреНрд╕реНрдЯ
    TypeScript рдПрдХреАрдХрд░рдгтЬЕ рдСрдЯреЛ-рдЬреЗрдирд░реЗрдЯреЗрдб рдкреНрд░рдХрд╛рд░тЬЕ AppConfig рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗтЬЕ рдЯрд╛рдЗрдкреНрдб рдореИрд╕реЗрдЬ рдлрд╝рдВрдХреНрд╢рдВрд╕тЪая╕П рдХреЗрд╡рд▓ рдореИрдХреНрд░реЛрдЬрд╝
    рдЧрд╛рдпрдм рдЕрдиреБрд╡рд╛рдж рдбрд┐рдЯреЗрдХреНрд╢рдитЬЕ рдЯрд╛рдЗрдк рдПрд░рд░ рдФрд░ рдмрд┐рд▓реНрдб рдЪреЗрддрд╛рд╡рдирд┐рдпрд╛рдБтЪая╕П рд░рдирдЯрд╛рдЗрдо рдлрд╝реЙрд▓рдмреИрдХтЪая╕П рдмреЗрд╕ рд▓реЛрдХреЗрд▓ рдкрд░ рдлрд╝реЙрд▓рдмреИрдХтЪая╕П рд╕реЛрд░реНрд╕ рдЯреЗрдХреНрд╕реНрдЯ рдкрд░ рдлрд╝реЙрд▓рдмреИрдХ
    рд░рд┐рдЪ рдХрдВрдЯреЗрдВрдЯ (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 рдЕрдиреБрд╡рд╛рджтЬЕ рдЖрдкрдХрд╛ рдЕрдкрдирд╛ рдкреНрд░рджрд╛рддрд╛ рдФрд░ рдХреА (key)тЭМ рдирд╣реАрдВтЭМ рдирд╣реАрдВтЭМ рдирд╣реАрдВ
    рд╡рд┐рдЬреБрдЕрд▓ рдПрдбрд┐рдЯрд░ / CMSтЬЕ рд╕реНрдерд╛рдиреАрдп рдПрдбрд┐рдЯрд░ + рд╡реИрдХрд▓реНрдкрд┐рдХ CMSтЭМ рдмрд╛рд╣рд░реА рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдотЪая╕П inlang рдЗрдХреЛрд╕рд┐рд╕реНрдЯрдо рдРрдкреНрд╕тЭМ рдмрд╛рд╣рд░реА рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдо
    SEO рд╣реЗрд▓реНрдкрд░реНрд╕ (hreflang, sitemap)тЬЕ рдЕрдВрддрд░реНрдирд┐рд╣рд┐рддтЭМ рдореИрдиреНрдпреБрдЕрд▓тЪая╕П рд╕реНрдерд╛рдиреАрдпрдХреГрдд URLs, рдмрд╛рдХреА рдореИрдиреНрдпреБрдЕрд▓тЭМ рдореИрдиреНрдпреБрдЕрд▓
    рд░рдирдЯрд╛рдЗрдо рд╕рд╛рдЗрдЬрд╝ (gzip, рдмреЗрдВрдЪрдорд╛рд░реНрдХ)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 рдЧрд╛рдЗрдбреНрд╕: Lingui, Paraglide JS, рдФрд░ Intlayerред

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

    • рдПрдХреНрд╕реЗрд╕рд┐рдмрд┐рд▓рд┐рдЯреА, рд╕реНрдХреНрд░реАрди рд░реАрдбрд░реНрд╕ рдФрд░ рд╕рд░реНрдЪ рдЗрдВрдЬрди рдХреЗ рд▓рд┐рдП <html> рдкрд░ lang рдФрд░ dir рд╕реЗрдЯ рдХрд░реЗрдВред
    • рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ URL рд░рдЦреЗрдВред рдХреЗрд╡рд▓-рдХреБрдХреА рд╕реНрд╡рд┐рдЪ рдХреЗ рдмрдЬрд╛рдп рд▓реЛрдХреЗрд▓ рдкреНрд░реАрдлрд┐рдХреНрд╕ (/fr/about) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ, рддрд╛рдХрд┐ рдкреНрд░рддреНрдпреЗрдХ рдЕрдиреБрд╡рд╛рджрд┐рдд рдкреЗрдЬ рдХреНрд░реЙрд▓ рдФрд░ рд╢реЗрдпрд░ рдХрд░рдиреЗ рдпреЛрдЧреНрдп рд╣реЛред
    • рдореИрд╕реЗрдЬреЗрд╕ рдХреЛ рдиреЗрдорд╕реНрдкреЗрд╕ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рд╡рд┐рднрд╛рдЬрд┐рдд рдХрд░реЗрдВ (common, home, about) рдФрд░ рдЙрдиреНрд╣реЗрдВ рдкреНрд░рддрд┐ рд░реВрдЯ рд▓реЛрдб рдХрд░реЗрдВред
    • рдХреЗрд╡рд▓ рд╕рдХреНрд░рд┐рдп рд▓реЛрдХреЗрд▓ рд▓реЛрдб рдХрд░реЗрдВред рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ рдореЙрдбреНрдпреВрд▓ рдореЗрдВ рдХрднреА рднреА рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдлрд╝рд╛рдЗрд▓ рдХреЛ рдЖрдпрд╛рдд рди рдХрд░реЗрдВред
    • IntlProvider рдореЗрдВ рдЯрд╛рдЗрдо рдЬрд╝реЛрди рдХреЛ рдлрд╝рд┐рдХреНрд╕ рдХрд░реЗрдВред рдЕрдиреНрдпрдерд╛ SSR рдХреЗ рджреМрд░рд╛рди рд╕рд░реНрд╡рд░ рдЯрд╛рдЗрдо рдЬрд╝реЛрди рдореЗрдВ рдФрд░ рд╣рд╛рдЗрдбреНрд░реЗрд╢рди рдкрд░ рд╡рд┐рдЬрд╝рд┐рдЯрд░ рдЯрд╛рдЗрдо рдЬрд╝реЛрди рдореЗрдВ рддрд┐рдерд┐рдпрд╛рдВ рдлрд╝реЙрд░реНрдореЗрдЯ рд╣реЛрддреА рд╣реИрдВ, рдЬрд┐рд╕рд╕реЗ рд╣рд╛рдЗрдбреНрд░реЗрд╢рди рдорд┐рд╕рдореИрдЪ рд╣реЛрддрд╛ рд╣реИред
    • рдЕрдкрдиреЗ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВ, рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдкрд░ canonical, hreflang рдФрд░ x-default рдШреЛрд╖рд┐рдд рдХрд░реЗрдВред
    • рдПрдХ рдмрд╣реБрднрд╛рд╖реА рд╕рд╛рдЗрдЯрдореИрдк рдФрд░ robots.txt рдЙрддреНрдкрдиреНрди рдХрд░реЗрдВ, рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреЛ рдкреНрд░реА-рд░реЗрдВрдбрд░ рдХрд░реЗрдВред
    • рд▓реЛрдХреЗрд▓ рд╕реНрд╡рд┐рдЪрд░ рдХреЗ рд▓рд┐рдП рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд▓рд┐рдВрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ, рди рдХрд┐ <select>, рддрд╛рдХрд┐ рдХреНрд░реЙрд▓рд░реНрд╕ рдкреНрд░рддреНрдпреЗрдХ рднрд╛рд╖рд╛ рдХреЛ рдЦреЛрдЬ рд╕рдХреЗрдВред
    • рдЕрдкрдиреЗ рдореИрд╕реЗрдЬреЗрд╕ рдХреЛ рдЯрд╛рдЗрдк рдХрд░реЗрдВ рддрд╛рдХрд┐ рдХреЛрдИ рднреА рдЧрд╛рдпрдм рдХреА (key) рдХрдВрдкрд╛рдЗрд▓ рд╕рдордп рдкрд░ рд╡рд┐рдлрд▓ рд╣реЛ рдЬрд╛рдПред
    рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдФрд░ SEO рдкрд░ рд╣рдорд╛рд░реА рдЧрд╛рдЗрдб рдФрд░ hreflang рдЧрд╛рдЗрдб рджреЗрдЦреЗрдВред

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

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

    bash
    .
    тФЬтФАтФА messages
    тФВ   тФЬтФАтФА en
    тФВ   тФВ   тФЬтФАтФА common.json
    тФВ   тФВ   тФЬтФАтФА home.json
    тФВ   тФВ   тФФтФАтФА about.json
    тФВ   тФЬтФАтФА fr
    тФВ   тФВ   тФФтФАтФА ... same files
    тФВ   тФФтФАтФА es
    тФВ       тФФтФАтФА ... same files
    тФЬтФАтФА vite.config.ts
    тФФтФАтФА src
        тФЬтФАтФА start.ts                  # Request middleware (locale redirect)
        тФЬтФАтФА router.tsx
        тФЬтФАтФА i18n
        тФВ   тФЬтФАтФА config.ts             # Locales, URL helpers
        тФВ   тФЬтФАтФА messages.ts           # Per-namespace, per-locale loader
        тФВ   тФЬтФАтФА negotiateLocale.ts    # Accept-Language parsing
        тФВ   тФЬтФАтФА seo.ts                # head() builder
        тФВ   тФФтФАтФА use-intl.d.ts         # Typed messages
        тФЬтФАтФА components
        тФВ   тФЬтФАтФА LocaleSwitcher.tsx
        тФВ   тФЬтФАтФА LocalizedLink.tsx
        тФВ   тФЬтФАтФА ScopedMessages.tsx
        тФВ   тФФтФАтФА Counter.tsx
        тФФтФАтФА routes
            тФЬтФАтФА __root.tsx
            тФЬтФАтФА sitemap[.]xml.ts
            тФЬтФАтФА robots[.]txt.ts
            тФФтФАтФА {-$locale}
                тФЬтФАтФА route.tsx         # Locale layout + IntlProvider
                тФЬтФАтФА index.tsx         # / and /fr
                тФЬтФАтФА about.tsx         # /about and /fr/about
                тФФтФАтФА $.tsx             # Localized 404
    
    1. рдирд┐рд░реНрднрд░рддрд╛рдПрдБ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ (Install Dependencies)

      TanStack Start рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд╕реЗ рд╢реБрд░реБрдЖрдд рдХрд░реЗрдВ, рдлрд┐рд░ use-intl рдЬреЛрдбрд╝реЗрдВ:

      bash
      npm create @tanstack/start@latest
      npm install use-intl
      
      • use-intl: IntlProvider, useTranslations, useFormatter рдФрд░ createTranslator (React рдХреЗ рдмрд╛рд╣рд░ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдпреЛрдЧреНрдп, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП head() рдореЗрдВ) рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред
    2. рдЕрдкрдиреЗ рд▓реЛрдХреЗрд▓ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХреЛ рдХреЗрдВрджреНрд░реАрдХреГрдд рдХрд░реЗрдВ

      рдЕрдкрдиреЗ рд▓реЛрдХреЗрд▓реНрд╕ рдФрд░ URL рд╣реЗрд▓реНрдкрд░реНрд╕ рдХреЗ рд▓рд┐рдП рд╕рд┐рдВрдЧрд▓ рд╕реЛрд░реНрд╕ рдСрдл рдЯреНрд░реБрде рдмрдирд╛рдПрдВред рд╣рд░ рджреВрд╕рд░реА рдлрд╝рд╛рдЗрд▓ (рд░реВрдЯреНрд╕, SEO, рд╕рд╛рдЗрдЯрдореИрдк, рдкреНрд░реА-рд░реЗрдВрдбрд░рд┐рдВрдЧ) рдпрд╣рд╛рдБ рд╕реЗ рдЖрдпрд╛рдд рдХрд░рддреА рд╣реИ, рдЗрд╕рд▓рд┐рдП рдПрдХ рд▓реЛрдХреЗрд▓ рдЬреЛрдбрд╝рдирд╛ рдПрдХ-рд▓рд╛рдЗрди рдХрд╛ рдмрджрд▓рд╛рд╡ рд╣реИред

      рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд▓реЛрдХреЗрд▓ рдмрд┐рдирд╛ рдкреНрд░реАрдлрд┐рдХреНрд╕ рдХреЗ рд░рд╣рддрд╛ рд╣реИ (/about), рдЕрдиреНрдп рд▓реЛрдХреЗрд▓реНрд╕ рдкреНрд░реАрдлрд┐рдХреНрд╕ рд╣реЛрддреЗ рд╣реИрдВ (/fr/about)ред рдпрд╣ "рдЖрд╡рд╢реНрдпрдХрддрд╛рдиреБрд╕рд╛рд░" (as-needed) рд░рдгрдиреАрддрд┐ рд╣реИ: рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдкреНрд░рддрд┐ рдкреЗрдЬ рдПрдХ URL, рдФрд░ рдЖрдкрдХреЗ рдореБрдЦреНрдп рджрд░реНрд╢рдХреЛрдВ рдХреЗ рд▓рд┐рдП рдЫреЛрдЯреЗ URLsред

      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. рдЕрдкрдиреА рдЕрдиреБрд╡рд╛рдж рдлрд╝рд╛рдЗрд▓реЗрдВ рдмрдирд╛рдПрдВ

      рдореИрд╕реЗрдЬреЗрд╕ рдХреЛ рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдФрд░ рдкреНрд░рддрд┐ рдиреЗрдорд╕реНрдкреЗрд╕ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд░реЗрдВред common рдЙрди рдЪреАрдЬрд╝реЛрдВ рдХреЛ рд░рдЦрддрд╛ рд╣реИ рдЬрд┐рдирдХреА рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдХреЛ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ (рдиреЗрд╡рд┐рдЧреЗрд╢рди, рдлреВрдЯрд░), рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдХреЛ рдЕрдкрдиреЗ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рд╕рд╣рд┐рдд рдЕрдкрдиреА рд╕реНрд╡рдпрдВ рдХреА рдлрд╝рд╛рдЗрд▓ рдорд┐рд▓рддреА рд╣реИред

      use-intl ICU MessageFormat рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдмрд╣реБрд╡рдЪрди, рдЪрдпрди рдФрд░ рд╕реНрд╡рд░реВрдкрд┐рдд рддрд░реНрдХ рд╕реНрд╡рдпрдВ рдореИрд╕реЗрдЬ рдХреЗ рдЕрдВрджрд░ рд░рд╣рддреЗ рд╣реИрдВред

      messages/en/common.json
      {
        "navigation": {
          "home": "Home",
          "about": "About"
        },
        "localeSwitcher": {
          "label": "Change language"
        },
        "notFound": {
          "title": "Page not found",
          "backHome": "Back to home"
        }
      }
      
      messages/en/about.json
      {
        "metadata": {
          "title": "About us",
          "description": "Learn who we are and why we built this application."
        },
        "title": "About us",
        "counter": {
          "label": "Counter",
          "increment": "Increment",
          "clicks": "{count, plural, =0 {No clicks yet} one {# click} other {# clicks}}"
        }
      }
      
      messages/fr/common.json
      {
        "navigation": {
          "home": "Accueil",
          "about": "├А propos"
        },
        "localeSwitcher": {
          "label": "Changer de langue"
        },
        "notFound": {
          "title": "Page introuvable",
          "backHome": "Retour ├а l'accueil"
        }
      }
      
      messages/fr/about.json
      {
        "metadata": {
          "title": "├А propos",
          "description": "D├йcouvrez qui nous sommes et pourquoi nous avons cr├й├й cette application."
        },
        "title": "├А propos",
        "counter": {
          "label": "Compteur",
          "increment": "Incr├йmenter",
          "clicks": "{count, plural, =0 {Aucun clic} one {# clic} other {# clics}}"
        }
      }
      

      рдЙрд╕реА рддрд░рд╣ home.json рдмрдирд╛рдПрдВ, рдЬрд┐рд╕рдореЗрдВ рдПрдХ metadata рдСрдмреНрдЬреЗрдХреНрдЯ рдФрд░ рдкреЗрдЬ рдХрдВрдЯреЗрдВрдЯ рд╣реЛред

    4. рдиреЗрдорд╕реНрдкреЗрд╕ рдФрд░ рд▓реЛрдХреЗрд▓ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рд╕рдВрджреЗрд╢ рд▓реЛрдб рдХрд░реЗрдВ

      рдпрд╣ рд▓реЛрдбрд░ рдкреНрд░рджрд░реНрд╢рди рдХреЗ рд▓рд┐рдП рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдлрд╝рд╛рдЗрд▓ рд╣реИред import.meta.glob Vite рдХреЛ рдкреНрд░рддрд┐ JSON рдлрд╝рд╛рдЗрд▓ рдПрдХ рдЪрдВрдХ рдЙрддреНрд╕рд░реНрдЬрд┐рдд рдХрд░рдиреЗ рдХрд╛ рдирд┐рд░реНрджреЗрд╢ рджреЗрддрд╛ рд╣реИред рдПрдХ рд░реВрдЯ рдЬреЛ рдлреНрд░реЗрдВрдЪ рдореЗрдВ ["about"] рдорд╛рдВрдЧрддрд╛ рд╣реИ, рд╡рд╣ messages/fr/about.json рдФрд░ рдХреБрдЫ рдирд╣реАрдВ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рддрд╛ рд╣реИ, рдЬрд┐рд╕рд╕реЗ рдмреЗрдВрдЪрдорд╛рд░реНрдХ 0% рд▓реЛрдХреЗрд▓ рд▓реАрдХ рдФрд░ 0% рдкреЗрдЬ рд▓реАрдХ рддрдХ рдкрд╣реБрдБрдЪ рдЬрд╛рддрд╛ рд╣реИред

      src/i18n/messages.ts
      import type about from "../../messages/en/about.json";
      import type common from "../../messages/en/common.json";
      import type home from "../../messages/en/home.json";
      import type { Locale } from "./config";
      
      /** Shape of every namespace, inferred from the English source files. */
      export type AppMessages = {
        common: typeof common;
        home: typeof home;
        about: typeof about;
      };
      
      export type Namespace = keyof AppMessages;
      
      type JsonModule = { default: AppMessages[Namespace] };
      
      // Lazy: each JSON file becomes its own chunk, loaded on demand
      const messageLoaders = import.meta.glob<JsonModule>("../../messages/*/*.json");
      
      /**
       * Loads the requested namespaces for one locale, in parallel.
       */
      export const loadMessages = async <
        const TNamespaces extends readonly Namespace[],
      >(
        locale: Locale,
        namespaces: TNamespaces
      ): Promise<Pick<AppMessages, TNamespaces[number]>> => {
        const entries = await Promise.all(
          namespaces.map(async (namespace) => {
            const loadNamespace =
              messageLoaders[`../../messages/${locale}/${namespace}.json`];
      
            if (!loadNamespace) {
              throw new Error(`Missing messages: ${locale}/${namespace}.json`);
            }
      
            const namespaceModule = await loadNamespace();
      
            return [namespace, namespaceModule.default] as const;
          })
        );
      
        return Object.fromEntries(entries) as Pick<AppMessages, TNamespaces[number]>;
      };
      
    5. рдЕрдкрдиреЗ рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рдЯрд╛рдЗрдк рдХрд░реЗрдВ (Type Your Messages)

      рдореЙрдбреНрдпреВрд▓ рдСрдЧреНрдореЗрдВрдЯреЗрд╢рди рдЖрдкрдХреЛ useTranslations("about") рдФрд░ t("counter.label") рдкрд░ рдСрдЯреЛ-рдХрдВрдкрд▓реАрд╢рди рджреЗрддрд╛ рд╣реИ, рдФрд░ рдХрд┐рд╕реА рднреА рдЯрд╛рдЗрдкреЛ рдпрд╛ рд╣рдЯрд╛рдИ рдЧрдИ рдХреА рдкрд░ рдХрдВрдкрд╛рдЗрд▓ рдПрд░рд░ рджреЗрддрд╛ рд╣реИред

      src/i18n/use-intl.d.ts
      import type { Locale } from "./config";
      import type { AppMessages } from "./messages";
      
      declare module "use-intl" {
        interface AppConfig {
          Locale: Locale;
          Messages: AppMessages;
        }
      }
      

      рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рдЖрдкрдХреЗ tsconfig.json рдореЗрдВ resolveJsonModule рд╕рдХреНрд╖рдо рд╣реИред

    6. рд░реВрдЯ рдбреЙрдХреНрдпреВрдореЗрдВрдЯ рдмрдирд╛рдПрдВ

      рд░реВрдЯ рд░реВрдЯ <html> рд░реЗрдВрдбрд░ рдХрд░рддрд╛ рд╣реИред рдпрд╣ lang рдФрд░ dir рд╕реЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡реИрдХрд▓реНрдкрд┐рдХ рд▓реЛрдХреЗрд▓ рдкреИрд░рдо рдХреЛ рдкрдврд╝рддрд╛ рд╣реИ, рддрд╛рдХрд┐ рдХреЛрдИ рднреА рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЪрд▓рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ, рд╕рд░реНрд╡рд░-рд░реЗрдВрдбрд░ рдХрд┐рдП рдЧрдП HTML рдореЗрдВ рд╡рд┐рд╢реЗрд╖рддрд╛рдПрдБ рд╕рд╣реА рд╣реЛрдВред

      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 }) {
        // strict: false reads params from whichever route is matched
        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 рд╕реЗ рдореЗрд▓ рдЦрд╛рддреЗ рд╣реИрдВред рдпрд╣ рд▓реЗрдЖрдЙрдЯ:

      1. рдЕрд╕рдорд░реНрдерд┐рдд рдкреНрд░реАрдлрд┐рдХреНрд╕ рдХреЛ рдЕрд╕реНрд╡реАрдХрд╛рд░ рдХрд░рддрд╛ рд╣реИ (/xx/about тЖТ 404)ред
      2. рдХреЗрд╡рд▓ рд╡рд░реНрддрдорд╛рди рд▓реЛрдХреЗрд▓ рдХреЗ рд▓рд┐рдП common рдиреЗрдорд╕реНрдкреЗрд╕ рд▓реЛрдб рдХрд░рддрд╛ рд╣реИред
      3. IntlProvider рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдореИрд╕реЗрдЬреЗрд╕ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред

      рд▓реЛрдбрд░ рдкрд░рд┐рдгрд╛рдо HTML рдореЗрдВ рд╕реАрд░рд┐рдпрд▓рд╛рдЗрдЬрд╝ рд╣реЛрддрд╛ рд╣реИ рдФрд░ рд╣рд╛рдЗрдбреНрд░реЗрд╢рди рдкрд░ рдкреБрди: рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдХреНрд▓рд╛рдЗрдВрдЯ рджреВрд╕рд░реА рдмрд╛рд░ common.json рдбрд╛рдЙрдирд▓реЛрдб рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред staleTime: Infinity рдЗрд╕реЗ рдХреНрд▓рд╛рдЗрдВрдЯ рдиреЗрд╡рд┐рдЧреЗрд╢рди рдХреЗ рджреМрд░рд╛рди рдХреИрд╢реНрдб рд░рдЦрддрд╛ рд╣реИред

      src/routes/{-$locale}/route.tsx
      import { createFileRoute, notFound, Outlet } from "@tanstack/react-router";
      import { IntlProvider } from "use-intl";
      import { Header } from "@/components/Header";
      import { NotFound } from "@/components/NotFound";
      import { isLocale, resolveLocale } from "@/i18n/config";
      import { loadMessages } from "@/i18n/messages";
      
      export const Route = createFileRoute("/{-$locale}")({
        beforeLoad: ({ params }) => {
          // /xx/about with an unknown prefix тЖТ 404
          if (params.locale !== undefined && !isLocale(params.locale)) {
            throw notFound();
          }
        },
        loader: async ({ params }) => {
          const locale = resolveLocale(params.locale);
      
          return { locale, messages: await loadMessages(locale, ["common"]) };
        },
        // Messages never change for a given locale
        staleTime: Infinity,
        component: LocaleLayout,
        notFoundComponent: NotFound,
      });
      
      function LocaleLayout() {
        const { locale, messages } = Route.useLoaderData();
      
        return (
          <IntlProvider
            locale={locale}
            messages={messages}
            // A fixed time zone prevents SSR / hydration date mismatches
            timeZone="UTC"
          >
            <Header />
            <main>
              <Outlet />
            </main>
          </IntlProvider>
        );
      }
      
      IntlProvider рдкреИрд░реЗрдВрдЯ рдкреНрд░реЛрд╡рд╛рдЗрдбрд░ рдХреЗ рдореИрд╕реЗрдЬреЗрд╕ рдХреЛ рдорд░реНрдЬ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рдЕрдЧрд▓рд╛ рд╕реНрдЯреЗрдк рдПрдХ рдЫреЛрдЯрд╛ рдХрдВрдкреЛрдиреЗрдВрдЯ рдЬреЛрдбрд╝рддрд╛ рд╣реИ рдЬреЛ рдРрд╕рд╛ рдХрд░рддрд╛ рд╣реИ, рддрд╛рдХрд┐ рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ common рдХреЗ рдКрдкрд░ рдЕрдкрдирд╛ рд╕реНрд╡рдпрдВ рдХрд╛ рдиреЗрдорд╕реНрдкреЗрд╕ рдЬреЛрдбрд╝ рд╕рдХреЗред
    8. рдкреЗрдЬ рдореИрд╕реЗрдЬреЗрд╕ рдХреЛ рд╕реНрдХреЛрдк рдХрд░реЗрдВ (Scope Page Messages)

      рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдЕрдкрдиреЗ рд▓реЛрдбрд░ рдореЗрдВ рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ рдиреЗрдорд╕реНрдкреЗрд╕ рдХреЛ рд▓реЛрдб рдХрд░рддрд╛ рд╣реИ, рдлрд┐рд░ рдЕрдкрдиреА рд╕рд╛рдордЧреНрд░реА рдХреЛ ScopedMessages рдХреЗ рд╕рд╛рде рд▓рдкреЗрдЯрддрд╛ рд╣реИ, рдЬреЛ рдкреЗрдЬ рдиреЗрдорд╕реНрдкреЗрд╕ рдХреЛ рдкреИрд░реЗрдВрдЯ рдореИрд╕реЗрдЬреЗрд╕ рдХреЗ рд╕рд╛рде рдорд░реНрдЬ рдХрд░рддрд╛ рд╣реИред

      src/components/ScopedMessages.tsx
      import { type ReactNode, useMemo } from "react";
      import {
        type AbstractIntlMessages,
        IntlProvider,
        useLocale,
        useMessages,
        useTimeZone,
      } from "use-intl";
      
      type ScopedMessagesProps = {
        messages: AbstractIntlMessages;
        children: ReactNode;
      };
      
      /**
       * Adds route-level namespaces on top of the messages already provided.
       */
      export const ScopedMessages = ({ messages, children }: ScopedMessagesProps) => {
        const parentMessages = useMessages();
        const locale = useLocale();
        const timeZone = useTimeZone();
      
        const mergedMessages = useMemo(
          () => ({ ...parentMessages, ...messages }),
          [parentMessages, messages]
        );
      
        return (
          <IntlProvider locale={locale} timeZone={timeZone} messages={mergedMessages}>
            {children}
          </IntlProvider>
        );
      };
      
    9. рдЕрдкрдиреЗ рдкреЗрдЬреЛрдВ рдореЗрдВ рдЕрдиреБрд╡рд╛рдж рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ

      рдкреЗрдЬ рд▓реЛрдбрд░ рд╡рд░реНрддрдорд╛рди рд▓реЛрдХреЗрд▓ рдХреЗ рд▓рд┐рдП about рдиреЗрдорд╕реНрдкреЗрд╕ рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИ, head() рдЗрд╕рд╕реЗ рдЕрдиреБрд╡рд╛рджрд┐рдд, SEO-рдкреВрд░реНрдг рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдмрдирд╛рддрд╛ рд╣реИ (рд╕реНрдЯреЗрдк 13 рджреЗрдЦреЗрдВ), рдФрд░ рдХрдВрдкреЛрдиреЗрдВрдЯ рд╕рд╛рдордЧреНрд░реА рдХреЛ рд░реЗрдВрдбрд░ рдХрд░рддрд╛ рд╣реИред

      src/routes/{-$locale}/about.tsx
      import { createFileRoute } from "@tanstack/react-router";
      import { createTranslator, useTranslations } from "use-intl";
      import { Counter } from "@/components/Counter";
      import { ScopedMessages } from "@/components/ScopedMessages";
      import { resolveLocale } from "@/i18n/config";
      import { loadMessages } from "@/i18n/messages";
      import { buildLocalizedHead } from "@/i18n/seo";
      
      export const Route = createFileRoute("/{-$locale}/about")({
        loader: async ({ params }) => ({
          messages: await loadMessages(resolveLocale(params.locale), ["about"]),
        }),
        staleTime: Infinity,
        head: ({ params, loaderData }) => {
          const locale = resolveLocale(params.locale);
      
          if (!loaderData) return {};
      
          // createTranslator works outside React, perfect for head()
          const t = createTranslator({
            locale,
            messages: loaderData.messages,
            namespace: "about.metadata",
          });
      
          return buildLocalizedHead({
            path: "/about",
            locale,
            title: t("title"),
            description: t("description"),
          });
        },
        component: AboutPage,
      });
      
      function AboutPage() {
        const { messages } = Route.useLoaderData();
      
        return (
          <ScopedMessages messages={messages}>
            <AboutContent />
          </ScopedMessages>
        );
      }
      
      function AboutContent() {
        const t = useTranslations("about");
      
        return (
          <>
            <h1>{t("title")}</h1>
            <Counter />
          </>
        );
      }
      
    10. рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдореЗрдВ рдЕрдиреБрд╡рд╛рдж рдФрд░ рдлреЙрд░реНрдореЗрдЯрд░реНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ

      рдкреНрд░реЛрд╡рд╛рдЗрдбрд░реНрд╕ рдХреЗ рдЕрдВрддрд░реНрдЧрдд рдХреЛрдИ рднреА рдХрдВрдкреЛрдиреЗрдВрдЯ useTranslations рдФрд░ useFormatter рдХреЛ рдХреЙрд▓ рдХрд░ рд╕рдХрддрд╛ рд╣реИред рдкреНрд▓реВрд░рд▓реНрд╕ ICU рджреНрд╡рд╛рд░рд╛ рд╣рд▓ рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВ, рдФрд░ рд╕рдВрдЦреНрдпрд╛рдПрдБ рд╕рдХреНрд░рд┐рдп рд▓реЛрдХреЗрд▓ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рд╕реНрд╡рд░реВрдкрд┐рдд рд╣реЛрддреА рд╣реИрдВред

      src/components/Counter.tsx
      import { useState } from "react";
      import { useFormatter, useTranslations } from "use-intl";
      
      export const Counter = () => {
        const t = useTranslations("about.counter");
        const format = useFormatter();
        const [count, setCount] = useState(0);
      
        return (
          <div>
            <p>{t("clicks", { count })}</p>
            <p>{format.number(count)}</p>
            <button
              type="button"
              aria-label={t("label")}
              onClick={() => setCount((value) => value + 1)}
            >
              {t("increment")}
            </button>
          </div>
        );
      };
      
    11. рдПрдХ рд╕реНрдерд╛рдиреАрдпрдХреГрдд рд▓рд┐рдВрдХ рдХрдВрдкреЛрдиреЗрдВрдЯ рдмрдирд╛рдПрдВ

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

      рдкреНрд░рддреНрдпреЗрдХ рд░реВрдЯ {-$locale} рдХреЗ рдЕрдВрддрд░реНрдЧрдд рд░рд╣рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдПрдХ рд▓рд┐рдВрдХ рдХреЛ рд╡рд░реНрддрдорд╛рди рд▓реЛрдХреЗрд▓ рдкреИрд░рдо рд▓реЗ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрд╣ рд░реИрдкрд░ TanStack Router рдХреЗ рдЯрд╛рдЗрдкреНрдб to рдХреЛ рд░рдЦрддрд╛ рд╣реИ рдФрд░ рдЖрдкрдХреЗ рд▓рд┐рдП рд▓реЛрдХреЗрд▓ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░рддрд╛ рд╣реИред

      src/components/LocalizedLink.tsx
      import { Link, type LinkComponentProps } from "@tanstack/react-router";
      import { useLocale } from "use-intl";
      import { toLocaleParam } from "@/i18n/config";
      
      type LocalizedLinkProps = Omit<LinkComponentProps, "params">;
      
      export const LocalizedLink = (props: LocalizedLinkProps) => {
        const locale = useLocale();
      
        return <Link {...props} params={{ locale: toLocaleParam(locale) }} />;
      };
      
      src/components/Header.tsx
      import { useTranslations } from "use-intl";
      import { LocaleSwitcher } from "./LocaleSwitcher";
      import { LocalizedLink } from "./LocalizedLink";
      
      export const Header = () => {
        const t = useTranslations("common.navigation");
      
        return (
          <header>
            <nav>
              <LocalizedLink to="/{-$locale}">{t("home")}</LocalizedLink>
              <LocalizedLink to="/{-$locale}/about">{t("about")}</LocalizedLink>
            </nav>
            <LocaleSwitcher />
          </header>
        );
      };
      
    12. рдЕрдкрдиреА рд╕рд╛рдордЧреНрд░реА рдХреА рднрд╛рд╖рд╛ рдмрджрд▓реЗрдВ

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

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

      src/components/LocaleSwitcher.tsx
      import { Link } from "@tanstack/react-router";
      import { useLocale, useTranslations } from "use-intl";
      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 = () => {
        const t = useTranslations("common.localeSwitcher");
        const activeLocale = useLocale();
      
        return (
          <nav aria-label={t("label")}>
            <ul>
              {locales.map((locale) => (
                <li key={locale}>
                  <Link
                    to="."
                    params={(previous) => ({
                      ...previous,
                      locale: toLocaleParam(locale),
                    })}
                    hrefLang={locale}
                    lang={locale}
                    aria-current={locale === activeLocale ? "page" : undefined}
                    onClick={() => persistLocale(locale)}
                  >
                    {getLocaleName(locale)}
                  </Link>
                </li>
              ))}
            </ul>
          </nav>
        );
      };
      
    13. рдЕрдкрдиреЗ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХрд╛ рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдХрд░реЗрдВ

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

      рдпрд╣реАрдВ рдкрд░ i18n рдХрд╛ рдлрд╛рдпрджрд╛ рдорд┐рд▓рддрд╛ рд╣реИ: рдкреНрд░рддреНрдпреЗрдХ рднрд╛рд╖рд╛ рд╕рдВрд╕реНрдХрд░рдг рдЕрдкрдиреЗ рджрдо рдкрд░ рд░реИрдВрдХ рдХрд░ рд╕рдХрддрд╛ рд╣реИред рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдХреЛ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП:

      • рдПрдХ рдЕрдиреБрд╡рд╛рджрд┐рдд <title> рдФрд░ description;
      • рд╕реНрд╡рдпрдВ рдХреА рдУрд░ рдЗрд╢рд╛рд░рд╛ рдХрд░рдиреЗ рд╡рд╛рд▓рд╛ рдПрдХ рдХреИрдиреЛрдирд┐рдХрд▓ URL (рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд▓реЛрдХреЗрд▓ рдХреА рдУрд░ рдирд╣реАрдВ);
      • рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рдПрдХ hreflang рдСрд▓реНрдЯрд░рдиреЗрдЯ, рд╕рд╛рде рд╣реА рдмреЗрдореЗрд▓ рднрд╛рд╖рд╛рдУрдВ рдХреЗ рд▓рд┐рдП x-default;
      • рд╕реЛрд╢рд▓ рдкреНрд░реАрд╡реНрдпреВрдЬрд╝ рджреНрд╡рд╛рд░рд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ Open Graph og:locale, og:locale:alternate рдФрд░ og:url;
      • inLanguage рдХреЗ рд╕рд╛рде JSON-LD, рдЬреЛ рд╕рд░реНрдЪ рдЗрдВрдЬрди рдФрд░ AI рд╕рд╣рд╛рдпрдХреЛрдВ рдХреЛ рдкреЗрдЬ рдХреА рднрд╛рд╖рд╛ рдХрд╛ рд╢реНрд░реЗрдп рджреЗрдиреЗ рдореЗрдВ рдорджрдж рдХрд░рддрд╛ рд╣реИред

      рдПрдХ рдПрдХрд▓ рд╣реЗрд▓реНрдкрд░ рдпрд╣ рд╕рдм рдмрдирд╛рддрд╛ рд╣реИ, рдЬрд┐рд╕рд╕реЗ рдкреЗрдЬ рдХреЛрдб рд╕рдВрдХреНрд╖рд┐рдкреНрдд рд░рд╣рддрд╛ рд╣реИ:

      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: [
            // Canonical: each locale is its own canonical page
            { rel: "canonical", href: url },
            // hreflang: every language version, including the current one
            ...locales.map((alternateLocale) => ({
              rel: "alternate",
              hrefLang: alternateLocale,
              href: getAbsoluteUrl(path, alternateLocale),
            })),
            // x-default: fallback for visitors whose language is not supported
            {
              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,
              }),
            },
          ],
        };
      };
      

      рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдХреЗ head() рдореЗрдВ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ, рдЬреИрд╕рд╛ рдХрд┐ рд╕реНрдЯреЗрдк 9 рдореЗрдВ рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╣реЛрдо рдкреЗрдЬ рдХреЗ рд▓рд┐рдП, path: "/" рдкрд╛рд╕ рдХрд░реЗрдВред

    14. рдЕрдкрдиреЗ рд╕рд╛рдЗрдЯрдореИрдк рдХрд╛ рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдХрд░реЗрдВ

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

      рдПрдХ рдмрд╣реБрднрд╛рд╖реА рд╕рд╛рдЗрдЯрдореИрдк рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреЗ рдкреНрд░рддреНрдпреЗрдХ URL рдХреЛ рд╕реВрдЪреАрдмрджреНрдз рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╡рд┐рд╖реНрдЯрд┐ xhtml:link рдХреЗ рд╕рд╛рде рдЕрдкрдиреЗ рд╕рднреА рдСрд▓реНрдЯрд░рдиреЗрдЯ рдШреЛрд╖рд┐рдд рдХрд░рддреА рд╣реИред Google рдЗрди рдПрдиреЛрдЯреЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдареАрдХ рдкреЗрдЬ рдХреЗ hreflang рдЯреИрдЧ рдХреА рддрд░рд╣ рдХрд░рддрд╛ рд╣реИ, рдЬреЛ рдЙрдиреНрд╣реЗрдВ рддрдм рдПрдХ рд╡рд┐рд╢реНрд╡рд╕рдиреАрдп рдмреИрдХрдЕрдк рдмрдирд╛рддрд╛ рд╣реИ рдЬрдм рдХрд┐рд╕реА рдкреЗрдЬ рдХреЛ рд╢рд╛рдпрдж рд╣реА рдХрднреА рдХреНрд░реЙрд▓ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

      TanStack Start рд╕рд░реНрд╡рд░ рд░реВрдЯреНрд╕ рдЖрдкрдХреЛ рдЗрд╕реЗ рдПрдХ рдлрд╝рд╛рдЗрд▓ рд░реВрдЯ рд╕реЗ рдкрд░реЛрд╕рдиреЗ рдХреА рд╕реБрд╡рд┐рдзрд╛ рджреЗрддреЗ рд╣реИрдВ:

      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" },
              }),
          },
        },
      });
      
    15. рдЕрдкрдиреЗ robots.txt рдХрд╛ рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг рдХрд░реЗрдВ

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

      рдирд┐рдЬреА рд░реВрдЯреНрд╕ рдкреНрд░рддреНрдпреЗрдХ рднрд╛рд╖рд╛ рдореЗрдВ рдореМрдЬреВрдж рд╣реЛрддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП Disallow рдирд┐рдпрдореЛрдВ рдХреЛ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░реАрдлрд┐рдХреНрд╕ рдХреЛ рдХрд╡рд░ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрджрд┐ рд╕реНрдЯрд╛рд░реНрдЯрд░ рдиреЗ рдПрдХ рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдИ рд╣реИ рддреЛ public/robots.txt рдХреЛ рд╣рдЯрд╛ рджреЗрдВ, рдлрд┐рд░ рдЗрд╕реЗ рдПрдХ рд░реВрдЯ рд╕реЗ рдкрд░реЛрд╕реЗрдВ:

      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 => {
        // /dashboard, /fr/dashboard, /es/dashboard...
        const disallowRules = privatePaths.flatMap((path) =>
          locales.map((locale) => `Disallow: ${localizePath(path, locale)}`)
        );
      
        return [
          "User-agent: *",
          "Allow: /",
          ...disallowRules,
          "",
          `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" },
              }),
          },
        },
      });
      
    16. рдкрд╣рд▓реА рдмрд╛рд░ рдЖрдиреЗ рд╡рд╛рд▓реЗ рдЖрдЧрдВрддреБрдХреЛрдВ рдХреЛ рдЙрдирдХреА рднрд╛рд╖рд╛ рдкрд░ рдкреБрдирд░реНрдирд┐рд░реНрджреЗрд╢рд┐рдд рдХрд░реЗрдВ

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

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

      src/i18n/negotiateLocale.ts
      import { isLocale, type Locale } from "./config";
      
      /**
       * Picks the best supported locale from an Accept-Language header.
       * "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 }) => {
          const { pathname } = new URL(request.url);
      
          if (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],
      }));
      
      рдПрдХ рд╡рд┐рдЬрд╝рд┐рдЯрд░ рдЬреЛ рд╕реНрд╡рд┐рдЪрд░ рдореЗрдВ рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдЕрдВрдЧреНрд░реЗрдЬреА рдЪреБрдирддрд╛ рд╣реИ, рдЙрд╕реЗ рдХреБрдХреА рдореЗрдВ locale=en рдорд┐рд▓рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдЙрдиреНрд╣реЗрдВ рдлрд┐рд░ рдХрднреА рд░реАрдбрд╛рдпрд░реЗрдХреНрдЯ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдкреВрд░реА рддрд░рд╣ рд╕реЗ рд╕реНрдЯреЗрдЯрд┐рдХ рдкрд░рд┐рдирд┐рдпреЛрдЬрди рдкрд░ (рд╕реНрдЯреЗрдк 18), / рдХреЛ рдПрдХ рдлрд╝рд╛рдЗрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рдкрд░реЛрд╕рд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдпрд╣ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдирд╣реАрдВ рдЪрд▓рддрд╛ рд╣реИ, рдЬреЛ рдХрд┐ рдареАрдХ рд╣реИ: рдкреЗрдЬ рд╕реБрд▓рдн рд░рд╣рддрд╛ рд╣реИ рдФрд░ рд╕реНрд╡рд┐рдЪрд░ рдмрд╛рдХреА рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред
    17. use-intl API рдмрдирд╛рдП рд░рдЦреЗрдВ, Intlayer рдХреЗ рд╕рд╛рде рд░рдирдЯрд╛рдЗрдо рдШрдЯрд╛рдПрдВ

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

      рдмреЗрдВрдЪрдорд╛рд░реНрдХ рджрд┐рдЦрд╛рддрд╛ рд╣реИ рдХрд┐ use-intl рд╕реЗрдЯрдЕрдк рдХрд╛ рд╕рдмрд╕реЗ рднрд╛рд░реА рд╣рд┐рд╕реНрд╕рд╛ рд╕реНрд╡рдпрдВ рд░рдирдЯрд╛рдЗрдо рд╣реИ (~76 KB gzip)ред @intlayer/use-intl рдХрдореНрдкреИрдЯ рдПрдбреЗрдкреНрдЯрд░ рд╕рдорд╛рди API (useTranslations, useFormatter, IntlProvider, createTranslator, ICU рдкреНрд▓реВрд░рд▓реНрд╕, t.rich) рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕реЗ рдХрдВрдкрд╛рдЗрд▓ рдХрд┐рдП рдЧрдП Intlayer рдбрд┐рдХреНрд╢рдирд░реА рд╕реЗ рдкрд░реЛрд╕рддрд╛ рд╣реИ: ~75.9 KB рдХреЗ рдмрдЬрд╛рдп ~6.7 KB, 0% рд▓реЛрдХреЗрд▓ рд▓реАрдХ рдФрд░ 0% рдкреЗрдЬ рд▓реАрдХ, рдЖрдкрдХреЗ рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдореЗрдВ рдмрд┐рдирд╛ рдХрд┐рд╕реА рдмрджрд▓рд╛рд╡ рдХреЗред

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

      Vite рдкреНрд▓рдЧрдЗрди use-intl рдХреЛ рдПрдбреЗрдкреНрдЯрд░ рдореЗрдВ рдЙрдкрдирд╛рдо (alias) рджреЗрддрд╛ рд╣реИ, рддрд╛рдХрд┐ рдореМрдЬреВрджрд╛ рдЖрдпрд╛рдд рдХрд╛рдо рдХрд░рддреЗ рд░рд╣реЗрдВ:

      vite.config.ts
      import useIntlVitePlugin from "@intlayer/use-intl/plugin";
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { defineConfig } from "vite";
      
      export default defineConfig({
        plugins: [tanstackStart(), viteReact(), useIntlVitePlugin()],
      });
      

      рдЖрдкрдХреА JSON рдлрд╝рд╛рдЗрд▓реЗрдВ sync JSON рдкреНрд▓рдЧрдЗрди рдХреА рдмрджреМрд▓рдд рд╕рд┐рдВрдЧрд▓ рд╕реЛрд░реНрд╕ рдСрдл рдЯреНрд░реБрде рдмрдиреА рд░рд╣рддреА рд╣реИрдВ:

      intlayer.config.ts
      import { syncJSON } from "@intlayer/sync-json-plugin";
      import { type IntlayerConfig, Locales } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
          defaultLocale: Locales.ENGLISH,
        },
        dictionary: {
          // One chunk per locale, loaded on demand
          importMode: "dynamic",
          format: "icu",
        },
        plugins: [
          syncJSON({
            format: "icu",
            source: ({ locale, key }) => `./messages/${locale}/${key}.json`,
          }),
        ],
      };
      
      export default config;
      
      рдпрд╣ рдПрдбреЗрдкреНрдЯрд░ рдПрдХ рдЖрд╕рд╛рди рдорд╛рдЗрдЧреНрд░реЗрд╢рди рдорд╛рд░реНрдЧ рднреА рд╣реИ: рдПрдХ рдмрд╛рд░ рдЬрдм рдпрд╣ рдЪрд▓рдиреЗ рд▓рдЧреЗ, рддреЛ рдЖрдк рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдХреЛ рдПрдХ-рдПрдХ рдХрд░рдХреЗ рдореВрд▓ useIntlayer API рдореЗрдВ рд▓реЗ рдЬрд╛ рд╕рдХрддреЗ рд╣реИрдВред Intlayer TanStack Start рдЧрд╛рдЗрдб рджреЗрдЦреЗрдВред
    18. рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдХреЛ рдкреНрд░реА-рд░реЗрдВрдбрд░ рдХрд░реЗрдВ

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

      рд╕реНрдЯреЗрдЯрд┐рдХ HTML рд╕рдмрд╕реЗ рддреЗрдЬрд╝ рдкреЗрдЬ рд╣реИ рдЬрд┐рд╕реЗ рдЖрдк рдкрд░реЛрд╕ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдЗрдВрдбреЗрдХреНрд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдмрд╕реЗ рдЖрд╕рд╛рди рд╣реИред рдкреНрд░рддреНрдпреЗрдХ рд╕реНрдерд╛рдиреАрдпрдХреГрдд рдкрд╛рде рдХреЛ рд╕реВрдЪреАрдмрджреНрдз рдХрд░реЗрдВ рддрд╛рдХрд┐ TanStack Start рдмрд┐рд▓реНрдб рд╕рдордп рдкрд░ рд╕рднреА рднрд╛рд╖рд╛ рд╕рдВрд╕реНрдХрд░рдгреЛрдВ, рд╕рд╛рде рд╣реА рд╕рд╛рдЗрдЯрдореИрдк рдФрд░ robots рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рдкреНрд░реА-рд░реЗрдВрдбрд░ рдХрд░реЗ:

      vite.config.ts
      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(),
        ],
      });
      

      рдЪреВрдВрдХрд┐ рд▓реЛрдХреЗрд▓ рд╕реНрд╡рд┐рдЪрд░ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд▓рд┐рдВрдХ рд░реЗрдВрдбрд░ рдХрд░рддрд╛ рд╣реИ, crawlLinks: true рдЙрди рдкреЗрдЬреЛрдВ рдХреЛ рднреА рдЦреЛрдЬ рд▓реЗрддрд╛ рд╣реИ рдЬрд┐рдиреНрд╣реЗрдВ рдЖрдк рд╕реВрдЪреАрдмрджреНрдз рдХрд░рдирд╛ рднреВрд▓ рдЧрдП рдереЗред

    19. рд╕реНрдерд╛рдиреАрдпрдХреГрдд 404 рдкреЗрдЬреЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓реЗрдВ

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

      рд╕реНрдЯреЗрдк 7 рдХрд╛ рд▓реЗрдЖрдЙрдЯ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдЕрдЬреНрдЮрд╛рдд рд▓реЛрдХреЗрд▓ рдкреНрд░реАрдлрд┐рдХреНрд╕ рдХреЗ рд▓рд┐рдП notFound() рдереНрд░реЛ рдХрд░рддрд╛ рд╣реИред рдПрдХ рдХреИрдЪ-рдСрд▓ рд░реВрдЯ рдЬреЛрдбрд╝реЗрдВ рддрд╛рдХрд┐ рд▓реЛрдХреЗрд▓ рдХреЗ рдЕрдВрджрд░ рдЕрдЬреНрдЮрд╛рдд рдкрд╛рде рднреА рд╕реНрдерд╛рдиреАрдпрдХреГрдд 404 рд░реЗрдВрдбрд░ рдХрд░реЗрдВ, рдФрд░ рдЗрд╕реЗ noindex рдХреЗ рд░реВрдк рдореЗрдВ рдЪрд┐рд╣реНрдирд┐рдд рдХрд░реЗрдВ: React 19 <meta> рдЯреИрдЧ рдХреЛ <head> рдореЗрдВ рд▓реЗ рдЬрд╛рддрд╛ рд╣реИред

      src/components/NotFound.tsx
      import { useTranslations } from "use-intl";
      import { LocalizedLink } from "./LocalizedLink";
      
      export const NotFound = () => {
        const t = useTranslations("common.notFound");
      
        return (
          <div>
            <meta name="robots" content="noindex" />
            <h1>{t("title")}</h1>
            <LocalizedLink to="/{-$locale}">{t("backHome")}</LocalizedLink>
          </div>
        );
      };
      
      src/routes/{-$locale}/$.tsx
      import { createFileRoute, notFound } from "@tanstack/react-router";
      
      // /fr/does/not/exist тЖТ rendered by the layout notFoundComponent
      export const Route = createFileRoute("/{-$locale}/$")({
        beforeLoad: () => {
          throw notFound();
        },
      });
      
    20. рд╕рд░реНрд╡рд░ рдлрд╝рдВрдХреНрд╢рдВрд╕ рдореЗрдВ рд▓реЛрдХреЗрд▓ рдПрдХреНрд╕реЗрд╕ рдХрд░реЗрдВ

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

      рд╕рд░реНрд╡рд░ рдлрд╝рдВрдХреНрд╢рдВрд╕ рдХреЛ рд░реВрдЯ рдкреИрд░рдореНрд╕ рдкреНрд░рд╛рдкреНрдд рдирд╣реАрдВ рд╣реЛрддреЗ рд╣реИрдВред рдПрдХ рд╕реНрдерд╛рдиреАрдпрдХреГрдд рдИрдореЗрд▓ рднреЗрдЬрдиреЗ рдпрд╛ рднрд╛рд╖рд╛ рдкреНрд░рд╛рдердорд┐рдХрддрд╛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд▓реЛрдХреЗрд▓ рдХреБрдХреА рдкрдврд╝реЗрдВ, рдФрд░ Accept-Language рд╣реЗрдбрд░ рдкрд░ рдлрд╝реЙрд▓рдмреИрдХ рдХрд░реЗрдВ:

      src/server/getServerLocale.ts
      import { createServerFn } from "@tanstack/react-start";
      import { getCookie, getRequestHeader } from "@tanstack/react-start/server";
      import { defaultLocale, isLocale, localeCookieName } from "@/i18n/config";
      import { negotiateLocale } from "@/i18n/negotiateLocale";
      
      export const getServerLocale = createServerFn().handler(() => {
        const cookieLocale = getCookie(localeCookieName);
      
        if (isLocale(cookieLocale)) return cookieLocale;
      
        return negotiateLocale(getRequestHeader("accept-language")) ?? defaultLocale;
      });
      

      рд╕рд░реНрд╡рд░ рдлрд╝рдВрдХреНрд╢рди рдХреЗ рдЕрдВрджрд░ рдЕрдиреБрд╡рд╛рдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЗрд╕реЗ use-intl рд╕реЗ loadMessages рдФрд░ createTranslator рдХреЗ рд╕рд╛рде рдЬреЛрдбрд╝реЗрдВред

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

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

      use-intl рдЕрдиреБрд╡рд╛рдж рд░реЗрдВрдбрд░ рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рдЙрдиреНрд╣реЗрдВ рдЙрддреНрдкрдиреНрди (produce) рдХрд░рдиреЗ рдореЗрдВ рдЖрдкрдХреА рд╕рд╣рд╛рдпрддрд╛ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред Intlayer рдирд┐рдГрд╢реБрд▓реНрдХ рдФрд░ рдУрдкрди рд╕реЛрд░реНрд╕ рд╣реИ, рдФрд░ рднрд▓реЗ рд╣реА рдЖрдк use-intl рдмрдирд╛рдП рд░рдЦреЗрдВ, рдпрд╣ рдЙрд╕ рдЕрдВрддрд░ рдХреЛ рднрд░рддрд╛ рд╣реИ:

      • CI рдпрд╛ рдпреВрдирд┐рдЯ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдореЗрдВ рдЧрд╛рдпрдм рдЕрдиреБрд╡рд╛рджреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░реЗрдВред рдЕрдкрдиреЗ рдЕрдиреБрд╡рд╛рджреЛрдВ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдирд╛ рджреЗрдЦреЗрдВред
      • рдЕрдкрдиреА рдЦреБрдж рдХреА API рдХреА рдФрд░ рдкреНрд░рджрд╛рддрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ AI рдХреЗ рд╕рд╛рде рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВ: npx intlayer fill рдЖрдкрдХреЗ рдРрдк рдХреЗ рд╕рдВрджрд░реНрдн рдХреЗ рд╕рд╛рде рдЧрд╛рдпрдм рдХреАрдЬрд╝ рдХрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд░рддрд╛ рд╣реИред рдСрдЯреЛ рдлрд┐рд▓ рдФрд░ CLI рджреЗрдЦреЗрдВред
      • sync JSON рдкреНрд▓рдЧрдЗрди рдХреЗ рд╕рд╛рде рдЕрдкрдиреА JSON рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рд╕рд┐рдВрдЧрд▓ рд╕реЛрд░реНрд╕ рдСрдл рдЯреНрд░реБрде рдХреЗ рд░реВрдк рдореЗрдВ рд░рдЦреЗрдВред
      • рд╡рд┐рдЬреБрдЕрд▓ рдПрдбрд┐рдЯрд░ рдФрд░ CMS рдХреЗ рд╕рд╛рде рд╕рд╛рдордЧреНрд░реА рдХреЛ рд╡рд┐рдЬреБрдЕрд▓ рд░реВрдк рд╕реЗ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ, рддрд╛рдХрд┐ рдЧреИрд░-рдбреЗрд╡рд▓рдкрд░реНрд╕ рднреА рдЕрдиреБрд╡рд╛рдж рдЕрдкрдбреЗрдЯ рдХрд░ рд╕рдХреЗрдВред
      • MCP рд╕рд░реНрд╡рд░ рдФрд░ рдПрдЬреЗрдВрдЯ рд╕реНрдХрд┐рд▓реНрд╕ рдХреЗ рд╕рд╛рде рдЕрдкрдиреЗ AI рдПрдЬреЗрдВрдЯ рдХреЛ рд╕рдВрджрд░реНрдн рджреЗрдВред
      • рд╕реНрдХреИрди рдХрдорд╛рдВрдб рдХреЗ рд╕рд╛рде рдЧрд╛рдпрдм hreflang, рдЧрд▓рдд рдХреИрдиреЛрдирд┐рдХрд▓ рдФрд░ рд▓реЛрдХреЗрд▓ рд▓реАрдХ рдХреЗ рд▓рд┐рдП рдЕрдкрдиреА рддреИрдирд╛рдд рд╕рд╛рдЗрдЯ рдХреЛ рд╕реНрдХреИрди рдХрд░реЗрдВред

      рд╕рднреА рд╕реБрд╡рд┐рдзрд╛рдУрдВ рдХреЛ рдЬрд╛рдирдиреЗ рдХреЗ рд▓рд┐рдП, Intlayer рдХреНрдпреЛрдВ рдЪреБрдиреЗрдВ рджреЗрдЦреЗрдВред

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

    рд╣рд╛рдБ, рдпрджрд┐ рдЖрдк Next.js рдХреЗ рдмрд╛рд╣рд░ next-intl API рдЪрд╛рд╣рддреЗ рд╣реИрдВред рдпрд╣ рдЖрдкрдХреЛ ICU рд╕рдВрджреЗрд╢, рдкреНрд░рд╛рд░реВрдкрдХ (formatters) рдФрд░ рдЕрдЪреНрдЫрд╛ TypeScript рд╕рдорд░реНрдерди рджреЗрддрд╛ рд╣реИ, рдФрд░ рдпрд╣ Next.js-рд╡рд┐рд╢рд┐рд╖реНрдЯ рдмрд╛рдзрд╛рдУрдВ рдЬреИрд╕реЗ setRequestLocale рд╕реЗ рдмрдЪрд╛рддрд╛ рд╣реИред рдЗрд╕рдХрд╛ рдирдХрд╛рд░рд╛рддреНрдордХ рдкрд╣рд▓реВ рд╡рдЬрд╝рди рд╣реИ: рдмреЗрдВрдЪрдорд╛рд░реНрдХ рд░рдирдЯрд╛рдЗрдо рдХреЗ рд▓рд┐рдП ~76 KB gzip рдорд╛рдкрддрд╛ рд╣реИ, рдФрд░ рдПрдХ рд╕рд╛рдорд╛рдиреНрдп рд╕реЗрдЯрдЕрдк рдкреНрд░рддреНрдпреЗрдХ рд▓реЛрдХреЗрд▓ рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдкреЗрдЬ рдХреЛ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рднреЗрдЬ рджреЗрддрд╛ рд╣реИред рд▓реАрдХ рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП, рдЗрд╕ рдЧрд╛рдЗрдб рдХреА рддрд░рд╣, рд░реВрдЯ рдФрд░ рд▓реЛрдХреЗрд▓ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдиреЗрдорд╕реНрдкреЗрд╕ рд▓реЛрдб рдХрд░реЗрдВред

    use-intl, next-intl рдХрд╛ рдХреЛрд░ рд╣реИред next-intl рд╢реАрд░реНрд╖ рдкрд░ Next.js рдПрдХреАрдХрд░рдг рдЬреЛрдбрд╝рддрд╛ рд╣реИ: рдПрдХ рдорд┐рдбрд▓рд╡реЗрдпрд░, рдиреЗрд╡рд┐рдЧреЗрд╢рди рд╣реЗрд▓реНрдкрд░реНрд╕, рд╕рд░реНрд╡рд░ рдХрдВрдкреЛрдиреЗрдВрдЯреНрд╕ рдХреЗ рд▓рд┐рдП getTranslations рдФрд░ рдЕрдиреБрд░реЛрдз рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рдиред TanStack Start рдкрд░ рдЖрдк рд╕реАрдзреЗ use-intl рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ рдФрд░ TanStack Router рдХреЗ рд╕рд╛рде рд░реВрдЯрд┐рдВрдЧ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВ, рдЬреИрд╕рд╛ рдХрд┐ рдКрдкрд░ рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред

    URL рдореЗрдВ рдкреНрд░реАрдлрд┐рдХреНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред рдЗрд╕ рддрд░рд╣ рдкреНрд░рддреНрдпреЗрдХ рднрд╛рд╖рд╛ рд╕рдВрд╕реНрдХрд░рдг рдХрд╛ рдЕрдкрдирд╛ URL рд╣реЛрддрд╛ рд╣реИ рдЬрд┐рд╕реЗ рд╕рд░реНрдЪ рдЗрдВрдЬрди рдЗрдВрдбреЗрдХреНрд╕ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╢реЗрдпрд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдПрдХ рд╕реНрдкрд╖реНрдЯ рд╡рд┐рдХрд▓реНрдк рдХреЛ рдпрд╛рдж рд░рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдХреБрдХреА рдЕрднреА рднреА рдЙрдкрдпреЛрдЧреА рд╣реИ, рдЬреЛ рдХрд┐ рд╕реНрдЯреЗрдк 16 рдХрд╛ рд░реАрдбрд╛рдпрд░реЗрдХреНрдЯ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХрд░рддрд╛ рд╣реИред

    рд╕рд░реНрд╡рд░ рдФрд░ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдЯрд╛рдЗрдо рдЬрд╝реЛрди рдореЗрдВ рддрд┐рдерд┐рдпреЛрдВ рдХреЛ рдлрд╝реЙрд░реНрдореЗрдЯ рдХрд░рддреЗ рд╣реИрдВред IntlProvider рдореЗрдВ рдПрдХ рд╕реНрдкрд╖реНрдЯ timeZone рдкрд╛рд╕ рдХрд░реЗрдВ (рдпрд╛ рдХреБрдХреА рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рд╡рд┐рдЬрд╝рд┐рдЯрд░ рдЯрд╛рдЗрдо рдЬрд╝реЛрди), рддрд╛рдХрд┐ рджреЛрдиреЛрдВ рдкрдХреНрд╖ рд╕рдорд╛рди рдЯреЗрдХреНрд╕реНрдЯ рдЙрддреНрдкрдиреНрди рдХрд░реЗрдВред

    рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рдиреЗрдорд╕реНрдкреЗрд╕ рджреНрд╡рд╛рд░рд╛ рд╡рд┐рднрд╛рдЬрд┐рдд рдХрд░реЗрдВ рдФрд░ рдЙрдиреНрд╣реЗрдВ import.meta.glob рдХреЗ рд╕рд╛рде рдкреНрд░рддрд┐ рд░реВрдЯ рдФрд░ рдкреНрд░рддрд┐ рд▓реЛрдХреЗрд▓ рд▓реЛрдб рдХрд░реЗрдВ, рдЬреЛ рд▓реЛрдХреЗрд▓ рдФрд░ рдкреЗрдЬ рд▓реАрдХ рдХреЛ рд╣рдЯрд╛ рджреЗрддрд╛ рд╣реИред рдлрд┐рд░, рдпрджрд┐ рд░рдирдЯрд╛рдЗрдо рд╕рд╛рдЗрдЬрд╝ рдорд╛рдпрдиреЗ рд░рдЦрддрд╛ рд╣реИ, рддреЛ @intlayer/use-intl рдПрдбреЗрдкреНрдЯрд░ рдкрд░ рд╕реНрд╡рд┐рдЪ рдХрд░реЗрдВ: рд╕рдорд╛рди API, рдмреЗрдВрдЪрдорд╛рд░реНрдХ рдореЗрдВ ~75.9 KB рдХреЗ рдмрдЬрд╛рдп ~6.7 KBред

    рд░реВрдЯ рд▓реЛрдбрд░ рджреНрд╡рд╛рд░рд╛ рд▓реМрдЯрд╛рдП рдЧрдП рд╕рдВрджреЗрд╢реЛрдВ рдХреЗ рд╕рд╛рде рд░реВрдЯ head() рдлрд╝рдВрдХреНрд╢рди рдХреЗ рдЕрдВрджрд░ createTranslator рдХреЛ рдХреЙрд▓ рдХрд░реЗрдВ, рдлрд┐рд░ title, description, рдХреИрдиреЛрдирд┐рдХрд▓ рдФрд░ hreflang рд▓рд┐рдВрдХ рд▓реМрдЯрд╛рдПрдВред рд╕реНрдЯреЗрдк 13 рдПрдХ рдкреБрди: рдкреНрд░рдпреЛрдЬреНрдп рд╣реЗрд▓реНрдкрд░ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред

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

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

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

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

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

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

    Aymeric PINEAU