Penulis:
    Dibuat:2026-09-26Terakhir diperbarui:2026-09-26

    Cara menginternasionalisasi aplikasi TanStack Start Anda menggunakan Paraglide JS pada tahun 2026

    Daftar Isi

    Apa itu Paraglide JS?

    Paraglide JS (oleh inlang) adalah pustaka i18n berbasis kompilator (compiler-based). Alih-alih mengirim runtime yang mencari kunci dalam objek JSON, pustaka ini mengompilasi setiap pesan menjadi fungsi JavaScript bertipe (m.about_title()). Pesan yang tidak digunakan dapat dihapus oleh bundler, dan kesalahan ketik pada kunci merupakan kesalahan kompilasi.

    Paraglide adalah pendekatan i18n yang digunakan dalam contoh resmi TanStack Router, dan terintegrasi dengan TanStack Start melalui tiga bagian:

    • sebuah plugin Vite yang mengompilasi pesan dan runtime ke dalam src/paraglide;
    • sebuah middleware server yang menentukan lokal dari setiap permintaan;
    • sebuah penulisan ulang router (router rewrite) yang memetakan URL yang dilokalkan (/fr/about) ke pohon rute Anda (/about), sehingga Anda tidak memerlukan segmen $locale.

    Panduan ini menyiapkan ketiganya, kemudian mencakup semua hal yang diserahkan Paraglide kepada Anda: lang dan dir, pengalih lokal (locale switcher), metadata yang diterjemahkan, canonical, hreflang dengan x-default, Open Graph, JSON-LD, sitemap, robots.txt, pra-rendering, dan halaman 404 yang dilokalkan.

    Mencari tumpukan lain? Lihat panduan TanStack Start + use-intl, panduan TanStack Start + Lingui, atau panduan TanStack Start + Intlayer.
    Membandingkan dua pendekatan berbasis kompilator? Baca apakah Intlayer lebih ringan daripada Paraglide?.

    Apa kata tolok ukur tentang Paraglide di TanStack Start

    Tolok ukur i18n menjalankan aplikasi TanStack Start 10 halaman dan 10 lokal yang sama dengan setiap pustaka utama dan mengukur apa yang sebenarnya diunduh oleh peramban.

    Pemuatan JSON dinamis

    Memuat terjemahan secara lambat saat runtime

    JSON cakupan (namespacing)

    Namespace terjemahan per halaman

    Tolok Ukur Performa I18n

    Apa metrik ini?

    Total ukuran kompresi gzip dari bundel pustaka internasionalisasi. Ini hanya mencakup penyedia dan logika pengambilan konten setelah tree-shaking dan minifikasi.

    Mengapa ini penting?

    Ukuran pustaka yang lebih kecil mengurangi muatan JavaScript awal, yang mengarah pada waktu unduh dan eksekusi yang lebih cepat pada klien.

    Lihat sebagai

    Angka-angka utama untuk @inlang/paraglide-js@2.15.1, diukur pada 2026-09-26 (gzip):

    PengaturanUkuran pustakaJS per halamanKebocoran lokal lainKebocoran halaman lainPemuatan halaman
    Tanpa i18n (aplikasi dasar)-111.0 KB0%0%15.7 ms
    Paraglide JS1.8 KB125.1 KB49.7%0%22.1 ms
    react-intlayer4.5 KB126.8 KB0%0%14.8 ms
    use-intl75.9 KB128.7 KB0%0%17.4 ms
    Lingui56.7 KB120.2 KB8.6%0%21.9 ms

    Poin penting yang perlu diperhatikan:

    • Runtime sangat kecil, dan halaman tidak bocor. Runtime dibuat untuk konfigurasi Anda, dan pesan diimpor di tempat pesan tersebut digunakan.
    • Lokal bocor. Setiap fungsi pesan berisi setiap lokal, sehingga sekitar setengah dari string terjemahan yang dikirim ke halaman berada dalam bahasa yang tidak digunakan pengunjung. Semakin banyak lokal yang Anda tambahkan, semakin besar bagian ini.
    • Pemuatan halaman adalah yang paling lambat dari kelompok ini, sebagian karena lokal ditentukan melalui strategi pada setiap panggilan daripada dibaca dari konteks React.
    Lihat data lengkap: Laporan tolok ukur TanStack Start, dan repositori tolok ukur.

    Perbandingan fitur di TanStack Start

    Perbandingan Paraglide JS dengan pustaka lain yang umum digunakan di TanStack Start:

    Fiturreact-intlayer (Intlayer)use-intlParaglide JSLingui
    Terjemahan dekat komponen✅ Ditempatkan bersama❌ JSON terpusat❌ Satu file JSON per lokal⚠️ Teks sumber dalam komponen
    Integrasi TypeScript✅ Tipe yang dibuat otomatis✅ Melalui AppConfig✅ Fungsi pesan bertipe⚠️ Hanya makro
    Deteksi terjemahan yang hilang✅ Kesalahan tipe dan peringatan build⚠️ Fallback runtime⚠️ Kembali ke lokal dasar⚠️ Kembali ke teks sumber
    Konten kaya (JSX, Markdown)✅ Dukungan langsung⚠️ Tag melalui t.rich⚠️ String✅ JSX di dalam <Trans>
    Perutean terlokalisasi✅ Bawaan❌ Manual {-$locale}✅ urlPatterns + penulisan ulang router❌ Manual {-$locale}
    Pengalihan lokal tanpa muat ulang✅ Ya✅ Ya❌ Muat ulang halaman penuh✅ Ya
    Pluralisasi✅ Berbasis enumerasi✅ ICU✅ Varian✅ ICU
    ICU MessageFormat✅ Melalui format: "icu"✅ Asli⚠️ Melalui plugin inlang✅ Asli
    Format konten✅ .ts, .json, .md, .yaml...⚠️ .json⚠️ inlang JSON✅ PO, JSON, CSV
    Terjemahan AI✅ Penyedia dan kunci Anda sendiri❌ Tidak❌ Tidak❌ Tidak
    Editor visual / CMS✅ Editor lokal + CMS opsional❌ Platform eksternal⚠️ Aplikasi ekosistem inlang❌ Platform eksternal
    Pembantu SEO (hreflang, sitemap)✅ Bawaan❌ Manual⚠️ URL terlokalisasi, sisanya manual❌ Manual
    Ukuran runtime (gzip, tolok ukur)4.5 KB75.9 KB1.8 KB56.7 KB
    Kebocoran, penyiapan terbaik (lokal / halaman)0% / 0%0% / 0%49.7% / 0%8.6% / 0%
    Terjemahan yang hilang di CI✅ npx intlayer test⚠️ Tidak bawaan⚠️ Tidak bawaan✅ lingui compile --strict
    Angka ukuran runtime dan kebocoran berasal dari tolok ukur TanStack Start. Kebocoran diukur pada penyiapan terbaik dari setiap pustaka.
    Panduan TanStack Start lainnya: Lingui, use-intl, dan Intlayer.

    Praktik yang harus Anda ikuti

    • Tetapkan lang dan dir pada <html> dari lokal yang telah ditentukan, di server.
    • Pertahankan satu URL per lokal dengan strategi awalan (/fr/about), sehingga setiap versi bahasa dapat diindeks.
    • Tempatkan url pertama dalam strategi lokal Anda, sehingga URL menjadi sumber kebenaran, dan perayap mendapatkan halaman yang mereka minta.
    • Gunakan kunci pesan yang datar dan deskriptif (about_title) yang dipetakan secara bersih ke nama fungsi.
    • Commit messages/*.json Anda, bukan folder src/paraglide yang dihasilkan, untuk menghindari konflik penggabungan pada file yang dibuat secara otomatis.
    • Terjemahkan metadata Anda, dan deklarasikan canonical, hreflang, serta x-default di setiap halaman.
    • Hasilkan sitemap dan robots.txt multibahasa, serta lakukan pra-rendering untuk setiap lokal.
    • Gunakan tautan nyata untuk pengalih lokal, sehingga perayap menemukan setiap bahasa.
    Lihat panduan kami tentang internasionalisasi dan SEO dan panduan hreflang.

    Panduan Langkah demi Langkah Menyiapkan Paraglide JS di Aplikasi TanStack Start

    Berikut struktur proyek yang akan kita buat:

    bash
    .
    ├── project.inlang
    │   └── settings.json          # Locales and message format
    ├── messages
    │   ├── en.json
    │   ├── fr.json
    │   └── es.json
    ├── vite.config.ts
    └── src
        ├── paraglide              # Generated, git-ignored
        ├── server.ts              # Paraglide middleware
        ├── router.tsx             # URL rewrite
        ├── i18n
        │   ├── config.ts          # Site URL, helpers
        │   └── seo.ts             # head() builder
        ├── components
        │   └── LocaleSwitcher.tsx
        └── routes
            ├── __root.tsx
            ├── index.tsx          # / and /fr
            ├── about.tsx          # /about and /fr/about
            ├── $.tsx              # Localized 404
            ├── sitemap[.]xml.ts
            └── robots[.]txt.ts
    

    Perhatikan bahwa tidak ada folder $locale: penulisan ulang router menghapus awalan sebelum pencocokan rute.

    1. Instal Dependensi

      Mulai dari proyek TanStack Start, lalu inisialisasi Paraglide. Perintah init membuat project.inlang/settings.json, sebuah file messages/en.json pertama, dan menginstal paket.

      bash
      npm create @tanstack/start@latest
      npx @inlang/paraglide-js@latest init
      
      • @inlang/paraglide-js: kompilator dan plugin Vite-nya. Tidak ada paket runtime untuk diinstal: runtime dihasilkan langsung ke dalam proyek Anda.
    2. Konfigurasikan Lokal Anda

      project.inlang/settings.json adalah satu-satunya sumber kebenaran untuk lokal. Plugin format pesan membaca satu file JSON per lokal.

      project.inlang/settings.json
      {
        "$schema": "https://inlang.com/schema/project-settings",
        "baseLocale": "en",
        "locales": ["en", "fr", "es"],
        "modules": [
          "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
          "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
        ],
        "plugin.inlang.messageFormat": {
          "pathPattern": "./messages/{locale}.json"
        }
      }
      
    3. Konfigurasikan Plugin Vite dan Strategi URL

      Plugin mengompilasi pesan pada setiap perubahan. Tiga opsi penting untuk TanStack Start:

      • strategy: daftar berurutan lokasi untuk membaca lokal. url di urutan pertama menjadikan URL sebagai sumber kebenaran. cookie dan preferredLanguage digunakan oleh middleware saat URL tidak menentukannya.
      • urlPatterns: cara lokal dipetakan ke URL. Lokal non-default dicantumkan terlebih dahulu, karena pola pencocokan pertama yang menang. Di sini lokal default tetap tanpa awalan (/about), dan lokal lain diberi awalan (/fr/about).
      • outputStructure: "message-modules": satu modul per pesan, yang memungkinkan bundler menghapus pesan yang tidak diimpor oleh halaman.
      vite.config.ts
      import { paraglideVitePlugin } from "@inlang/paraglide-js";
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { defineConfig } from "vite";
      
      export default defineConfig({
        plugins: [
          paraglideVitePlugin({
            project: "./project.inlang",
            outdir: "./src/paraglide",
            outputStructure: "message-modules",
            cookieName: "PARAGLIDE_LOCALE",
            strategy: ["url", "cookie", "preferredLanguage", "baseLocale"],
            urlPatterns: [
              {
                pattern: "/:path(.*)?",
                localized: [
                  ["fr", "/fr/:path(.*)?"],
                  ["es", "/es/:path(.*)?"],
                  // Default locale last: it matches every remaining URL
                  ["en", "/:path(.*)?"],
                ],
              },
            ],
          }),
          tanstackStart(),
          viteReact(),
        ],
      });
      

      Tambahkan folder yang dihasilkan ke .gitignore. Folder ini dibangun kembali saat dev dan build:

      .gitignore
      src/paraglide
      
    4. Buat File Terjemahan Anda

      Setiap kunci menjadi fungsi yang diekspor dari src/paraglide/messages. Kunci snake_case yang datar memberikan nama fungsi yang paling bersih. Variabel menggunakan placeholder {name}.

      messages/en.json
      {
        "$schema": "https://inlang.com/schema/inlang-message-format",
        "nav_home": "Home",
        "nav_about": "About",
        "locale_switcher_label": "Change language",
        "home_meta_title": "Welcome",
        "home_meta_description": "A multilingual TanStack Start application.",
        "home_title": "Hello {name}!",
        "about_meta_title": "About us",
        "about_meta_description": "Learn who we are and why we built this application.",
        "about_title": "About us",
        "not_found_title": "Page not found",
        "not_found_back_home": "Back to home"
      }
      
      messages/fr.json
      {
        "$schema": "https://inlang.com/schema/inlang-message-format",
        "nav_home": "Accueil",
        "nav_about": "À propos",
        "locale_switcher_label": "Changer de langue",
        "home_meta_title": "Bienvenue",
        "home_meta_description": "Une application TanStack Start multilingue.",
        "home_title": "Bonjour {name} !",
        "about_meta_title": "À propos",
        "about_meta_description": "Découvrez qui kami sommes et pourquoi kami avons créé cette application.",
        "about_title": "À propos",
        "not_found_title": "Page introuvable",
        "not_found_back_home": "Retour à l'accueil"
      }
      

      Bentuk jamak menggunakan sintaks varian dari format pesan inlang:

      messages/en.json
      {
        "cart_items": [
          {
            "declarations": ["input count", "local countPlural = count: plural"],
            "selectors": ["countPlural"],
            "match": {
              "countPlural=one": "{count} item",
              "countPlural=other": "{count} items"
            }
          }
        ]
      }
      
    5. Tambahkan Middleware Server

      Middleware menentukan lokal dari setiap permintaan dengan strategi Anda, dan membuatnya tersedia untuk getLocale() di seluruh proses rendering server, melalui cakupan AsyncLocalStorage. Hal inilah yang membuat permintaan bersamaan dalam bahasa berbeda tetap aman.

      Di TanStack Start, bungkus server entry default:

      src/server.ts
      import handler from "@tanstack/react-start/server-entry";
      import { paraglideMiddleware } from "./paraglide/server";
      
      export default {
        fetch(request: Request): Promise<Response> {
          return paraglideMiddleware(request, () => handler.fetch(request));
        },
      };
      
    6. Tulis Ulang URL yang Dilokalkan di Router

      Opsi rewrite TanStack Router menerjemahkan URL di batas router:

      • input: /fr/about di-delokalisasi menjadi /about sebelum pencocokan, sehingga satu rute about.tsx melayani setiap bahasa;
      • output: setiap href yang dihasilkan (tautan, pengalihan, navigasi) dilokalkan untuk lokal yang aktif, sehingga <Link to="/about"> merender /fr/about pada halaman bahasa Prancis.
      src/router.tsx
      import { createRouter } from "@tanstack/react-router";
      import { deLocalizeUrl, localizeUrl } from "./paraglide/runtime";
      import { routeTree } from "./routeTree.gen";
      
      export const getRouter = () =>
        createRouter({
          routeTree,
          scrollRestoration: true,
          rewrite: {
            input: ({ url }) => deLocalizeUrl(url),
            output: ({ url }) => localizeUrl(url),
          },
        });
      
      declare module "@tanstack/react-router" {
        interface Register {
          router: ReturnType<typeof getRouter>;
        }
      }
      
      Karena tautan dilokalkan oleh penulisan ulang, Anda tidak memerlukan komponen kustom LocalizedLink: gunakan Link TanStack Router seperti biasa.
    7. Buat Dokumen Root

      getLocale() mengembalikan lokal yang ditentukan oleh middleware di server, dan lokal dari URL di peramban, sehingga lang dan dir identik di HTML server dan setelah hidrasi.

      src/i18n/config.ts
      import { baseLocale, type Locale, localizeUrl } from "@/paraglide/runtime";
      
      /** Public origin, used for canonical URLs, hreflang and the sitemap. */
      export const siteUrl = "https://example.com";
      
      /** Open Graph expects `language_TERRITORY` codes. */
      export const openGraphLocales: Record<Locale, string> = {
        en: "en_US",
        fr: "fr_FR",
        es: "es_ES",
      };
      
      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";
      
      /** `getAbsoluteUrl("/about", "fr")` → `https://example.com/fr/about` */
      export const getAbsoluteUrl = (path: string, locale: Locale): string =>
        localizeUrl(new URL(path, siteUrl), { locale }).href;
      
      export const getLocaleName = (locale: Locale): string =>
        new Intl.DisplayNames([locale], { type: "language" }).of(locale) ?? locale;
      
      export { baseLocale };
      
      src/routes/__root.tsx
      import {
        createRootRoute,
        HeadContent,
        Link,
        Outlet,
        Scripts,
      } from "@tanstack/react-router";
      import type { ReactNode } from "react";
      import { LocaleSwitcher } from "@/components/LocaleSwitcher";
      import { NotFound } from "@/components/NotFound";
      import { getTextDirection } from "@/i18n/config";
      import { m } from "@/paraglide/messages";
      import { getLocale } from "@/paraglide/runtime";
      
      export const Route = createRootRoute({
        head: () => ({
          meta: [
            { charSet: "utf-8" },
            { name: "viewport", content: "width=device-width, initial-scale=1" },
          ],
        }),
        shellComponent: RootDocument,
        component: RootLayout,
        notFoundComponent: NotFound,
      });
      
      function RootDocument({ children }: { children: ReactNode }) {
        const locale = getLocale();
      
        return (
          <html lang={locale} dir={getTextDirection(locale)}>
            <head>
              <HeadContent />
            </head>
            <body>
              {children}
              <Scripts />
            </body>
          </html>
        );
      }
      
      function RootLayout() {
        return (
          <>
            <header>
              <nav>
                <Link to="/">{m.nav_home()}</Link>
                <Link to="/about">{m.nav_about()}</Link>
              </nav>
              <LocaleSwitcher />
            </header>
            <main>
              <Outlet />
            </main>
          </>
        );
      }
      
    8. Gunakan Terjemahan di Halaman Anda

      Pesan adalah fungsi biasa: impor m, panggil fungsinya, teruskan variabel sebagai objek. Semuanya bertipe, termasuk variabel.

      src/routes/index.tsx
      import { createFileRoute } from "@tanstack/react-router";
      import { buildLocalizedHead } from "@/i18n/seo";
      import { m } from "@/paraglide/messages";
      import { getLocale } from "@/paraglide/runtime";
      
      export const Route = createFileRoute("/")({
        head: () =>
          buildLocalizedHead({
            path: "/",
            locale: getLocale(),
            title: m.home_meta_title(),
            description: m.home_meta_description(),
          }),
        component: HomePage,
      });
      
      function HomePage() {
        return <h1>{m.home_title({ name: "TanStack" })}</h1>;
      }
      
      src/routes/about.tsx
      import { createFileRoute } from "@tanstack/react-router";
      import { buildLocalizedHead } from "@/i18n/seo";
      import { m } from "@/paraglide/messages";
      import { getLocale } from "@/paraglide/runtime";
      
      export const Route = createFileRoute("/about")({
        head: () =>
          buildLocalizedHead({
            path: "/about",
            locale: getLocale(),
            title: m.about_meta_title(),
            description: m.about_meta_description(),
          }),
        component: AboutPage,
      });
      
      function AboutPage() {
        return <h1>{m.about_title()}</h1>;
      }
      
      Fungsi pesan juga menerima lokal eksplisit: m.about_title({}, { locale: "fr" }). Ini berguna dalam kode server yang merender bahasa selain yang diminta, seperti email.
    9. Ubah Bahasa Konten Anda

      Opsional

      Render pengalih sebagai tautan dengan localizeHref, sehingga perayap menemukan setiap bahasa. setLocale menyimpan pilihan dalam cookie dan memuat ulang halaman dalam bahasa baru: muat ulang penuh adalah perilaku yang diharapkan dari Paraglide, karena fungsi pesan membaca lokal pada setiap panggilan alih-alih berlangganan state React.

      src/components/LocaleSwitcher.tsx
      import { useLocation } from "@tanstack/react-router";
      import { getLocaleName } from "@/i18n/config";
      import { m } from "@/paraglide/messages";
      import {
        getLocale,
        type Locale,
        locales,
        localizeHref,
        setLocale,
      } from "@/paraglide/runtime";
      
      export const LocaleSwitcher = () => {
        // Router pathname, already de-localized by the rewrite: "/about"
        const { pathname } = useLocation();
        const activeLocale = getLocale();
      
        const handleClick = (event: React.MouseEvent, locale: Locale) => {
          event.preventDefault();
          setLocale(locale); // Sets the cookie and reloads on the localized URL
        };
      
        return (
          <nav aria-label={m.locale_switcher_label()}>
            <ul>
              {locales.map((locale) => (
                <li key={locale}>
                  <a
                    href={localizeHref(pathname, { locale })}
                    hrefLang={locale}
                    lang={locale}
                    aria-current={locale === activeLocale ? "page" : undefined}
                    onClick={(event) => handleClick(event, locale)}
                  >
                    {getLocaleName(locale)}
                  </a>
                </li>
              ))}
            </ul>
          </nav>
        );
      };
      
    10. Internasionalisasikan Metadata Anda

      Opsional

      Setiap versi bahasa dapat diberi peringkat secara mandiri, asalkan setiap halaman menyediakan:

      • <title> dan description yang diterjemahkan;
      • URL canonical yang mengarah ke dirinya sendiri;
      • satu alternatif hreflang per lokal, ditambah x-default;
      • Open Graph og:locale, og:locale:alternate dan og:url;
      • JSON-LD dengan inLanguage.

      localizeUrl Paraglide membangun URL alternatif dari urlPatterns Anda, sehingga tidak akan pernah melenceng dari perutean yang sebenarnya:

      src/i18n/seo.ts
      import { baseLocale, type Locale, locales } from "@/paraglide/runtime";
      import { getAbsoluteUrl, openGraphLocales } from "./config";
      
      type LocalizedHeadOptions = {
        /** De-localized path, 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, baseLocale),
            },
          ],
          scripts: [
            {
              type: "application/ld+json",
              children: JSON.stringify({
                "@context": "https://schema.org",
                "@type": "WebPage",
                name: title,
                description,
                url,
                inLanguage: locale,
              }),
            },
          ],
        };
      };
      
    11. Internasionalisasikan Sitemap Anda

      Opsional

      Sitemap multibahasa mencantumkan setiap URL dari setiap lokal, dan setiap entri mendeklarasikan semua alternatifnya dengan xhtml:link:

      src/routes/sitemap[.]xml.ts
      import { createFileRoute } from "@tanstack/react-router";
      import { getAbsoluteUrl } from "@/i18n/config";
      import { baseLocale, locales } from "@/paraglide/runtime";
      
      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, baseLocale)}"/>`,
        ].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" },
              }),
          },
        },
      });
      
    12. Internasionalisasikan robots.txt Anda

      Opsional

      Rute privat ada di setiap bahasa, jadi aturan Disallow harus mencakup setiap jalur yang dilokalkan. Hapus public/robots.txt jika template pemula membuatnya, lalu sajikan dari sebuah rute:

      src/routes/robots[.]txt.ts
      import { createFileRoute } from "@tanstack/react-router";
      import { siteUrl } from "@/i18n/config";
      import { locales, localizeHref } from "@/paraglide/runtime";
      
      const privatePaths = ["/dashboard", "/admin"];
      
      const buildRobots = (): string => {
        // /dashboard, /fr/dashboard, /es/dashboard...
        const disallowRules = privatePaths.flatMap((path) =>
          locales.map((locale) => `Disallow: ${localizeHref(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" },
              }),
          },
        },
      });
      
    13. Lakukan Pra-rendering Setiap Lokal

      Opsional

      Cantumkan jalur yang dilokalkan dari setiap halaman sehingga TanStack Start melakukan pra-rendering semua versi bahasa. localizeHref adalah kode yang dihasilkan tanpa dependensi peramban, sehingga dapat berjalan di vite.config.ts, tetapi file tersebut hanya ada setelah kompilasi pertama. Mencantumkan jalur secara manual, seperti di bawah ini, menghindari masalah urutan tersebut:

      vite.config.ts
      import { paraglideVitePlugin } from "@inlang/paraglide-js";
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { defineConfig } from "vite";
      
      const prefixedLocales = ["fr", "es"]; // Default locale "en" is unprefixed
      const pagePaths = ["/", "/about"];
      
      const localizedPages = pagePaths.flatMap((path) => [
        path,
        ...prefixedLocales.map((locale) =>
          path === "/" ? `/${locale}` : `/${locale}${path}`
        ),
      ]);
      
      export default defineConfig({
        plugins: [
          paraglideVitePlugin({
            // ... same options as step 3
            project: "./project.inlang",
            outdir: "./src/paraglide",
          }),
          tanstackStart({
            prerender: { enabled: true, crawlLinks: true },
            pages: [
              ...localizedPages.map((path) => ({
                path,
                prerender: { enabled: true },
              })),
              { path: "/sitemap.xml", prerender: { enabled: true } },
              { path: "/robots.txt", prerender: { enabled: true } },
            ],
          }),
          viteReact(),
        ],
      });
      

      Karena pengalih merender tautan nyata, crawlLinks: true juga menemukan halaman yang lupa Anda cantumkan.

    14. Tangani Halaman 404 yang Dilokalkan

      Opsional

      Dengan penulisan ulang, /fr/does-not-exist dicocokkan sebagai /does-not-exist, dan getLocale() tetap mengembalikan fr, sehingga notFoundComponent root dari langkah 7 dirender dalam bahasa Prancis. Rute catch-all memastikan jalur bertingkat dalam juga mencapainya. Tandai halaman dengan noindex: React 19 mengangkat <meta> ke <head>.

      src/components/NotFound.tsx
      import { Link } from "@tanstack/react-router";
      import { m } from "@/paraglide/messages";
      
      export const NotFound = () => (
        <div>
          <meta name="robots" content="noindex" />
          <h1>{m.not_found_title()}</h1>
          <Link to="/">{m.not_found_back_home()}</Link>
        </div>
      );
      
      src/routes/$.tsx
      import { createFileRoute, notFound } from "@tanstack/react-router";
      
      export const Route = createFileRoute("/$")({
        beforeLoad: () => {
          throw notFound();
        },
      });
      
    15. Akses Lokal dalam Server Functions

      Opsional

      Server functions berjalan di dalam cakupan middleware Paraglide, jadi getLocale() juga berfungsi di sana:

      src/server/sendWelcomeEmail.ts
      import { createServerFn } from "@tanstack/react-start";
      import { m } from "@/paraglide/messages";
      import { getLocale } from "@/paraglide/runtime";
      
      export const sendWelcomeEmail = createServerFn({ method: "POST" })
        .inputValidator((email: string) => email)
        .handler(async ({ data: email }) => {
          const locale = getLocale();
          const subject = m.home_meta_title({}, { locale });
      
          // await mailer.send({ to: email, subject, locale });
          return { email, subject, locale };
        });
      
    16. Bandingkan dengan Intlayer

      Opsional

      Tidak ada adaptor langsung dari Paraglide ke Intlayer, karena keduanya mengikuti ide yang sama: mengompilasi konten pada waktu build dan mengirim runtime sesedikit mungkin. Perbedaannya terletak pada apa yang sampai ke peramban dan bagaimana konten diatur:

      • Lokal: Intlayer memuat kamus dinamis per lokal (kebocoran lokal 0% dalam tolok ukur), sementara setiap fungsi pesan Paraglide membawa setiap lokal (49.7%).
      • Pengorganisasian konten: konten dapat berada dalam file .content.ts di sebelah setiap komponen, atau dalam file terpusat. Lihat i18n per komponen vs terpusat.
      • Pengalihan lokal: konten dibaca dari konteks React, sehingga beralih lokal merender ulang tanpa memuat ulang halaman.
      • Kode yang dihasilkan: tidak ada yang dihasilkan di dalam src, jadi tidak ada yang perlu dibuat ulang sebelum commit.

      Jika Anda berasal dari pustaka lain selain Paraglide, adaptor kompatibilitas mempertahankan API use-intl, next-intl, react-i18next, react-intl, atau Lingui dan mengganti runtime-nya.

      Lihat apakah Intlayer lebih ringan daripada Paraglide? dan panduan TanStack Start Intlayer.

    17. Otomatiskan Terjemahan Anda Menggunakan Intlayer

      Opsional

      Paraglide merender terjemahan, tetapi tidak membantu Anda membuatnya. Intlayer gratis dan sumber terbuka, dan perkakasnya membantu bahkan pada proyek Paraglide:

      • Terjemahkan dengan AI menggunakan kunci API dan penyedia Anda sendiri. Lihat pengisian otomatis dan CLI.
      • Pertahankan file JSON Anda sebagai sumber kebenaran dengan plugin sync JSON.
      • Uji terjemahan yang hilang di CI. Lihat menguji terjemahan Anda.
      • Pindai situs Anda yang telah diterapkan untuk mencari hreflang yang hilang, canonical yang salah, dan kebocoran lokal dengan perintah scan.

    Pertanyaan yang Sering Diajukan

    Pilihan yang solid: digunakan dalam contoh resmi TanStack Router, memiliki runtime terkecil dalam tolok ukur (~1.8 KB gzip), dan pesan bertipe lengkap. Komprominya adalah setiap fungsi pesan berisi semua lokal, yang membocorkan sekitar setengah dari string terjemahan kepada pengunjung bahasa lain, serta perpindahan lokal memerlukan muat ulang halaman.

    Tidak. rewrite router menghapus awalan lokal sebelum pencocokan rute dan menambahkannya kembali ke tautan yang dihasilkan, sehingga satu about.tsx melayani /about, /fr/about, dan /es/about.

    Fungsi pesan membaca lokal saat dipanggil, fungsi tersebut tidak berlangganan state React. Oleh karena itu, setLocale memuat ulang halaman secara default, sehingga setiap pesan dirender ulang dalam bahasa baru. Anda dapat meneruskan { reload: false }, tetapi Anda harus merender ulang struktur pohon secara manual.

    Lebih baik tidak. Folder ini dibuat ulang pada setiap dev dan build, dan meng-commit-nya menyebabkan konflik penggabungan pada file yang dibuat secara otomatis. Commit messages/*.json dan project.inlang/settings.json sebagai gantinya.

    Gunakan localizeUrl untuk membuat satu URL absolut per lokal di rute head(), dan tambahkan x-default yang mengarah ke lokal dasar. Langkah 10 menyediakan helper yang dapat digunakan kembali, dan langkah 11 menambahkan alternatif yang sama ke sitemap.

    Pesan yang tidak digunakan akan dihapus saat Anda menggunakan outputStructure: "message-modules", sehingga konten halaman lain tidak bocor. Namun lokal yang tidak digunakan tidak dihapus: setiap fungsi pesan berisi setiap terjemahan, itulah sebabnya tolok ukur mengukur kebocoran lokal sebesar 49.7%.

    Komentar

    Belum ada komentar. Jadilah yang pertama membagikan pemikiran Anda.

    Postingan Terkait

    Postingan Terbaru