Author: Aymeric PINEAU
    Creation:2026-04-20Last update:2026-05-18

    Biblioteki i18n dla TanStack Start - Raport Benchmark 2026

    Ta strona to raport z benchmarku rozwiązań i18n w TanStack Start.

    Spis Treści

    Interaktywny Benchmark

    Referencja wyników:

    intlayer.org
    Zobacz pełne dane benchmarku

    Zobacz pełne repozytorium benchmarka tutaj.

    Wprowadzenie

    Rozwiązania internaucjonalizacji należą do najcięższych zależności w aplikacji React. W TanStack Start głównym ryzykiem jest przesyłanie niepotrzebnych treści: tłumaczeń dla innych stron i innych lokalizacji w pakiecie pojedynczej trasy.

    W miarę rozwoju aplikacji problem ten może szybko spowodować gwałtowny wzrost ilości kodu JavaScript wysyłanego do klienta i spowolnić nawigację.

    W praktyce, w przypadku najmniej zoptymalizowanych implementacji, strona zinternacjonalizowana może okazać się kilka razy cięższa niż wersja bez i18n.

    Inny wpływ dotyczy doświadczenia programisty (DX): sposobu deklarowania treści, typów, organizacji przestrzeni nazw, ładowania dynamicznego i reaktywności przy zmianie lokalizacji.

    TL;DR

    • Intlayer: Zapewnia najlepszą wydajność i najmniejszy rozmiar pakietu (v8.7.12) dla TanStack Start.
    • react-i18next & use-intl: Dojrzałe alternatywy z dużymi ekosystemami, ale znacznie cięższe i bardziej złożone w optymalizacji.
    • Paraglide: Innowacyjny pomysł na tree-shaking, który nie działa w praktyce. Skomplikowane DX i narzut reaktywności w TanStack Start.
    • Unikaj: General Translation (GT) i Lingo.dev ze względu na poważne problemy z wydajnością, limity AI i uzależnienie od dostawcy (vendor lock-in).

    Przetestuj swoją aplikację

    Aby szybko wykryć problemy z wyciekami i18n, przygotowałem darmowy skaner dostępny tutaj.

    intlayer.org

    Problem

    Dwie dźwignie są niezbędne do ograniczenia kosztów wielojęzycznej aplikacji:

    • Podział treści na strony / przestrzenie nazw, aby nie ładować całych słowników, gdy nie są one potrzebne.
    • Dynamiczne ładowanie odpowiedniej lokalizacji tylko wtedy, gdy jest to potrzebne.

    Zrozumienie technicznych ograniczeń tych podejść:

    Ładowanie dynamiczne

    Bez dynamicznego ładowania większość rozwiązań przechowuje komunikaty w pamięci od pierwszego renderowania, co dodaje znaczny narzut w przypadku aplikacji z wieloma trasami i lokalizacjami.

    Wraz z ładowaniem dynamicznym akceptujesz kompromis: mniej początkowego kodu JS, ale czasami dodatkowe żądanie przy zmianie języka.

    Podział treści (Content splitting)

    Składnie oparte na const t = useTranslation() + t('a.b.c') są bardzo wygodne, ale często zachęcają do utrzymywania dużych obiektów JSON w czasie wykonywania. Ten model utrudnia usuwanie kodu (tree-shaking), chyba że biblioteka oferuje realną strategię podziału na strony.

    Metodologia

    W tym benchmarku porównaliśmy następujące biblioteki:

    • Base App (Brak biblioteki i18n)
    • react-intlayer (v8.7.12)
    • react-i18next (v17.0.2)
    • use-intl (v4.9.1)
    • @lingui/core (v5.3.0)
    • @inlang/paraglide-js (v2.15.1)
    • @tolgee/react (v7.0.0)
    • react-intl (v10.1.1)
    • wuchale (v0.22.11)
    • gt-react (vlatest)
    • lingo.dev (v0.133.9)

    Frameworkiem jest TanStack Start z wielojęzyczną aplikacją składającą się z 10 stron i 10 języków.

    Porównaliśmy cztery strategie ładowania:

    Strategia Brak przestrzeni nazw (globalna) Z przestrzeniami nazw (scoped)
    Ładowanie statyczne Static: Wszystko w pamięci przy starcie. Scoped static: Podział na przestrzenie nazw; wszystko ładowane przy starcie.
    Ładowanie dynamiczne Dynamic: Ładowanie na żądanie na lokalizację. Scoped dynamic: Szczegółowe ładowanie na przestrzeń nazw i lokalizację.

    Podsumowanie strategii

    • Static: Prosta; brak opóźnień sieciowych po początkowym załadowaniu. Minus: duży rozmiar pakietu.
    • Dynamic: Redukuje początkową wagę (lazy-loading). Idealna w przypadku wielu lokalizacji.
    • Scoped static: Utrzymuje porządek w kodzie (separacja logiczna) bez skomplikowanych dodatkowych żądań sieciowych.
    • Scoped dynamic: Najlepsze podejście pod kątem dzielenia kodu (code splitting) i wydajności. Minimalizuje zużycie pamięci, ładując tylko to, czego potrzebuje bieżący widok i aktywna lokalizacja.

    Gwiazdki na GitHubie

    Gwiazdki na GitHubie są silnym wskaźnikiem popularności projektu, zaufania społeczności i długoterminowego znaczenia. Choć nie są bezpośrednią miarą jakości technicznej, odzwierciedlają, ilu programistów uważa projekt za przydatny, śledzi jego postępy i prawdopodobnie go przyjmie. Przy szacowaniu wartości projektu gwiazdki pomagają porównać zainteresowanie alternatywami i dostarczają wglądu w rozwój ekosystemu.

    Star History Chart

    Szczegóły wyników

    1 - Rozwiązania, których należy unikać

    Należy wyraźnie unikać niektórych rozwiązań, takich jak gt-react czy lingo.dev. Łączą one uzależnienie od dostawcy z zaśmiecaniem bazy kodu. Co gorsza: mimo wielu godzin spędzonych na próbach ich wdrożenia, nigdy nie udało mi się sprawić, by działały poprawnie w TanStack Start (podobnie jak w przypadku gt-next w Next.js).

    Napotkane problemy:

    (General Translation) (gt-react@latest):

    • W przypadku aplikacji o rozmiarze około 110 KB, gt-react może dodać ponad 440 KB ekstra (rząd wielkości zaobserwowany w implementacji Next.js w tym samym benchmarku).
    • Komunikat Quota Exceeded, please upgrade your plan przy pierwszej próbie budowania z General Translation.
    • Tłumaczenia nie są renderowane; otrzymuję błąd Error: <T> used on the client-side outside of <GTProvider>, co wydaje się być błędem w bibliotece.
    • Podczas wdrażania gt-tanstack-start-react napotkałem również problem z biblioteką: błąd does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser, który powodował awarię aplikacji. Po zgłoszeniu opiekun naprawił go w ciągu 24 godzin.
    • Biblioteki te stosują anty-wzorzec poprzez funkcję initializeGT(), blokując możliwość czystego usuwania kodu z pakietu (tree-shaking).

    (Lingo.dev) ([email protected]):

    • Przekroczenie limitu AI (lub blokująca zależność serwerowa), co sprawia, że budowanie / produkcja są ryzykowne bez płacenia.
    • Kompilator pomijał niemal 40% przetłumaczonej treści. Musiałem przepisać wszystkie struktury .map na płaskie bloki komponentów, aby go uruchomić.
    • Ich CLI jest zabugowane i miało tendencję do resetowania pliku konfiguracyjnego bez powodu.
    • Podczas budowania całkowicie wymazywało wygenerowane pliki JSON, gdy dodawana była nowa treść. W rezultacie kilka kluczy mogło wymazać setki istniejących kluczy.
    • Napotkałem problemy z reaktywnością biblioteki w TanStack Start: przy zmianie lokalizacji musiałem wymusić ponowne renderowanie dostawcy, aby działała.

    2 - Rozwiązania eksperymentalne

    (Wuchale) ([email protected]):

    Idea stojąca za Wuchale jest interesująca, ale nie jest to jeszcze opłacalne rozwiązanie. Napotkałem problemy z reaktywnością tej biblioteki i musiałem wymusić ponowne renderowanie dostawcy, aby aplikacja działała w TanStack Start. Dokumentacja jest również dość niejasna, co utrudnia wdrożenie.

    3 - Akceptowalne rozwiązania

    (Paraglide) (@inlang/[email protected]):

    Paraglide oferuje innowacyjne i przemyślane podejście. Mimo to w tym benchmarku reklamowany przez firmę tree-shaking nie zadziałał w mojej implementacji Next.js ani w TanStack Start. Przepływ pracy i DX są również bardziej złożone niż w przypadku innych opcji. Osobiście nie jestem fanem konieczności regenerowania plików JS przed każdym przesłaniem kodu (push), co stwarza ciągłe ryzyko konfliktów scalania dla programistów przy Pull Requestach.

    Uwaga dotycząca paraglide: to rozwiązanie wstrzykuje kod do bazy kodu dla importów, w wyniku czego metryka 'lib size' w raporcie benchmarku wynosi prawie 0. Generowanie kodu jest dobre, ponieważ użyta funkcja będzie zawierać tylko niezbędną logikę (prefiks wszędzie vs brak prefiksu, cookie vs pamięć itp.). Dla porównania, Intlayer wykonuje to filtrowanie poprzez wstrzykiwanie zmiennych środowiskowych w procesie budowania, aby wymusić na bundlerze tree-shaking zawartości w zależności od logiki. Dzięki temu paraglide i intlayer są ostatecznie od 6 do 10 razy lżejszymi rozwiązaniami niż i18next czy next-intl.

    (Tolgee) (@tolgee/[email protected]):

    Tolgee rozwiązuje wiele z wymienionych wcześniej problemów. Uznałem jednak, że trudniej z nim zacząć niż z innymi narzędziami o podobnym podejściu. Nie zapewnia on bezpieczeństwa typów, co sprawia, że bardzo trudno jest wyłapać brakujące klucze w czasie kompilacji. Musiałem opakować API Tolgee we własne API, aby dodać wykrywanie brakujących kluczy.

    W TanStack Start miałem również problemy z reaktywnością: przy zmianie lokalizacji musiałem wymusić ponowne renderowanie dostawcy i zasubskrybować zdarzenia zmiany lokalizacji, aby ładowanie w innym języku zachowywało się poprawnie.

    (use-intl) ([email protected]):

    use-intl jest obecnie najmodniejszym elementem „intl” w ekosystemie React (z tej samej rodziny co next-intl) i jest często forsowany przez agentów AI, ale moim zdaniem niesłusznie w środowisku, w którym liczy się wydajność. Rozpoczęcie pracy jest dość proste. W praktyce proces optymalizacji i ograniczania wycieków jest dość złożony. Podobnie połączenie dynamicznego ładowania + przestrzeni nazw + typów TypeScript bardzo spowalnia rozwój.

    W TanStack Start unikasz pułapek specyficznych dla Next.js (setRequestLocale, statyczne renderowanie), ale główny problem pozostaje ten sam: bez ścisłej dyscypliny pakiet szybko przenosi zbyt wiele komunikatów, a utrzymanie przestrzeni nazw dla każdej trasy staje się uciążliwe.

    (react-i18next) ([email protected]):

    react-i18next jest prawdopodobnie najpopularniejszą opcją, ponieważ był jedną z pierwszych, która zaspokoiła potrzeby i18n w aplikacjach JavaScript. Posiada on również szeroki zestaw wtyczek społecznościowych dla konkretnych problemów.

    Mimo to ma on te same główne wady co stosy zbudowane na t('a.b.c'): optymalizacje są możliwe, ale bardzo czasochłonne, a duże projekty niosą ze sobą ryzyko wpadnięcia w złe praktyki (przestrzenie nazw + ładowanie dynamiczne + typy).

    Różnią się również formaty komunikatów: use-intl używa ICU MessageFormat, podczas gdy i18next używa własnego formatu - co komplikuje narzędzia lub migracje, jeśli się je miesza.

    (Lingui) (@lingui/[email protected]):

    Lingui jest często chwalony. Osobiście uznałem, że proces pracy wokół lingui extract / lingui compile jest bardziej złożony niż w przypadku innych podejść, bez wyraźnej przewagi w tym benchmarku TanStack Start. Zauważyłem również niespójne składnie, które mylą AI (np. t(), t'', i18n.t(), <Trans>).

    (react-intl) ([email protected]):

    react-intl to wydajna implementacja zespołu Format.js. DX pozostaje rozwlekły: const intl = useIntl() + intl.formatMessage({ id: "xx.xx" }) dodaje złożoność, dodatkową pracę JavaScript i wiąże globalną instancję i18n z wieloma węzłami w drzewie React.

    4 - Rekomendacje

    Ten benchmark TanStack Start nie ma bezpośredniego odpowiednika next-translate (wtyczka Next.js + getStaticProps). Dla zespołów, które naprawdę chcą API t() z dojrzałym ekosystemem, react-i18next i use-intl pozostają „rozsądnymi” wyborami, ale przygotuj się na zainwestowanie dużej ilości czasu w optymalizację, aby uniknąć wycieków.

    (Intlayer) ([email protected]):

    Nie będę osobiście oceniał react-intlayer ze względu na obiektywizm, ponieważ jest to moje własne rozwiązanie.

    Notatka osobista

    Ta notatka jest osobista i nie wpływa na wyniki benchmarku. Mimo to w świecie i18n często widać konsensus wokół wzorca takiego jak const t = useTranslation('xx') + <>{t('xx.xx')}</> dla treści tłumaczonych.

    W aplikacjach React wstrzykiwanie funkcji jako ReactNode jest w mojej ocenie anty-wzorcem. Dodaje to również zbędną złożoność i narzut wykonania JavaScript (nawet jeśli prawie niezauważalny).