Strona głównaPiaskownicaPrezentacjaAplikacjaDokumentacjaBlog
    • Englishangielski
      EN
    • Русскийrosyjski
      RU
    • 日本語japoński
      JA
    • françaisfrancuski
      FR
    • 한국어koreański
      KO
    • 中文chiński
      ZH
    • Españolhiszpański
      ES
    • Deutschniemiecki
      DE
    • العربيةarabski
      AR
    • Italianowłoski
      IT
    • British Englishangielski brytyjski
      EN-GB
    • Portuguêsportugalski
      PT
    • हिन्दीhindi
      HI
    • Türkçeturecki
      TR
    • polskipolski
      PL
    • Indonesiaindonezyjski
      ID
    • Tiếng Việtwietnamski
      VI
    • Українськаukraiński
      UK
    /
    Alt+←
    Co to jest internacjonalizacja (i18n)?
    SEO dan i18n
    Przewodnik
    • i18n przy użyciu next-i18next
    • i18n przy użyciu next-intl
    Użyj Intlayer w swoim rozwiązaniu
    • Automatyzacja next-i18next
    • Automatyzacja react-i18next
    • Automatyzacja next-intl
    • Automatyzacja react-intl
    • Automatyzacja vue-i18n
    Porównania
    • next-i18next vs next-intl vs Intlayer
    • react-i18next vs react-intl vs Intlayer
    Dokumentacja
    1. Blog
    2. Per component vs centralized i18n
    Creation:2025-09-10Last update:2025-09-10
    Prześlij ten dokument do swojego ulubionego asystenta AI
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    Zadaj pytanie i otrzymaj streszczenie dokumentu, odwołując się do tej strony i wybranego dostawcy AI

    Treść tej strony została przetłumaczona przy użyciu sztucznej inteligencji.

    Zobacz ostatnią wersję oryginalnej treści w języku angielskim
    Edit this doc

    If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.

    GitHub link to the documentation
    Copy

    Copy doc Markdown to clipboard

    i18n per-komponentowy kontra scentralizowany

    Podejście per-komponentowe nie jest nowym pojęciem. Na przykład w ekosystemie Vue vue-i18n obsługuje i18n SFC (Single File Component). Nuxt również oferuje tłumaczenia per-component, a Angular wykorzystuje podobny wzorzec poprzez swoje Feature Modules.

    Nawet w aplikacji Flutter często można znaleźć taki wzorzec:

    bash
    Kopiuj kod

    Skopiuj kod do schowka

    lib/└── features/    └── login/        ├── login_screen.dart        └── login_screen.i18n.dart  # <- Tłumaczenia znajdują się tutaj
    lib/features/login/login_screen.i18n.dart
    Kopiuj kod

    Skopiuj kod do schowka

    import 'package:i18n_extension/i18n_extension.dart';extension Localization on String {  static var _t = Translations.byText("en") +      {        "Hello": {          "en": "Hello",          "fr": "Bonjour",        },      };  String get i18n => localize(this, _t);}

    Jednak w świecie React głównie widzimy różne podejścia, które pogrupuję w trzy kategorie:

    Podejście scentralizowane (i18next, next-intl, react-intl, lingui)

    • (bez namespaces) zakłada pojedyncze źródło pobierania treści. Domyślnie ładujesz treści ze wszystkich stron przy uruchomieniu aplikacji.

    Drobnoziarniste podejście (intlayer, inlang)

    • pobieranie treści w sposób drobnoziarnisty, na poziomie klucza lub komponentu.

    W tym wpisie na blogu nie będę się skupiał na rozwiązaniach opartych na kompilatorze, które już omówiłem tutaj: Kompilator vs deklaratywne i18n. Zwróć uwagę, że i18n oparty na kompilatorze (np. Lingui) jedynie automatyzuje ekstrakcję i ładowanie treści. Pod maską często ma te same ograniczenia co inne podejścia.

    Im bardziej drobnoziarnie kontrolujesz pobieranie treści, tym większe ryzyko wprowadzenia dodatkowego stanu i logiki do Twoich komponentów.

    Podejścia granularne są bardziej elastyczne niż scentralizowane, ale często wiąże się to z kompromisem. Nawet jeśli biblioteki reklamują "tree shaking", w praktyce często i tak skończysz ładując stronę we wszystkich językach.

    Ogólnie rzecz biorąc, decyzja wygląda mniej więcej tak:

    • Jeśli Twoja aplikacja ma więcej stron niż języków, warto preferować podejście granularne.
    • Jeśli masz więcej języków niż stron, powinieneś skłaniać się ku podejściu scentralizowanemu.

    Oczywiście autorzy bibliotek zdają sobie sprawę z tych ograniczeń i proponują obejścia. Wśród nich: dzielenie na namespaces, dynamiczne ładowanie plików JSON (await import()), albo oczyszczanie zawartości podczas procesu budowania.

    Jednocześnie powinieneś wiedzieć, że gdy dynamicznie ładujesz swoją zawartość, wprowadzasz dodatkowe żądania do serwera. Każde dodatkowe useState lub hook oznacza dodatkowe żądanie do serwera.

    Aby rozwiązać ten problem, Intlayer proponuje grupowanie wielu definicji treści pod tym samym kluczem. Intlayer następnie scali tę zawartość.

    Ale z tych wszystkich rozwiązań jasno wynika, że najbardziej popularnym podejściem jest podejście scentralizowane.

    Dlaczego więc podejście scentralizowane jest tak popularne?

    • Po pierwsze, i18next było pierwszym rozwiązaniem, które zyskało szerokie zastosowanie, podążając za filozofią inspirowaną architekturami PHP i Java (MVC), które opierają się na ścisłym rozdziale odpowiedzialności (trzymaniu treści oddzielnie od kodu). Pojawiło się w 2011 roku, ustanawiając swoje standardy jeszcze przed masowym przejściem na architektury oparte na komponentach (takie jak React).
    • Po drugie, gdy biblioteka zostanie szeroko przyjęta, trudno jest przestawić ekosystem na inne wzorce.
    • Stosowanie podejścia scentralizowanego ułatwia też pracę w systemach zarządzania tłumaczeniami (TMS) takich jak Crowdin, Phrase czy Localized.
    • Logika stojąca za podejściem per-component jest bardziej złożona niż w podejściu scentralizowanym i wymaga więcej czasu na rozwój, zwłaszcza gdy trzeba rozwiązywać problemy takie jak identyfikacja miejsca, w którym znajduje się dany content.

    Ok, ale dlaczego nie pozostać przy podejściu scentralizowanym?

    Pozwól, że wyjaśnię, dlaczego może to być problematyczne dla Twojej aplikacji:

    • Nieużywane dane: Gdy ładuje się strona, często pobierane są treści ze wszystkich pozostałych stron. (W aplikacji z 10 stronami to 90% załadowanej zawartości jest nieużywane). Lazy-loadujesz modal? Biblioteka i18n i tak na to nie zważa, i tak najpierw ładuje stringi.
    • Wydajność: Przy każdym re-renderze każdy komponent zostaje obciążony olbrzymim ładunkiem JSON, co negatywnie wpływa na reaktywność aplikacji w miarę jej rozrostu.
    • Utrzymanie: Utrzymywanie dużych plików JSON jest uciążliwe. Musisz skakać między plikami, aby dodać tłumaczenie, upewniając się, że żadne tłumaczenia nie są pominięte i że nie pozostają żadne orphan keys.
    • Design-system: To powoduje niekompatybilność z design systemami (np. komponent LoginForm) i ogranicza możliwość duplikowania komponentów między różnymi aplikacjami.

    "Ale wymyśliliśmy Namespaces!"

    Oczywiście, to ogromny krok naprzód. Przyjrzyjmy się porównaniu rozmiaru głównego bundle'a dla konfiguracji Vite + React + React Router v7 + Intlayer. Zasymulowaliśmy aplikację z 20 stronami.

    Pierwszy przykład nie uwzględnia leniwego ładowania tłumaczeń dla każdego locale i nie stosuje podziału na namespaces. Drugi obejmuje content purging + dynamiczne ładowanie tłumaczeń.

    Pokaż całą zawartość tabeli

    Otwórz tabelę w oknie modalnym, aby wyraźnie zobaczyć całą zawartość

    Zoptymalizowany bundle Bundle bez optymalizacji
    bundle bez optymalizacji zoptymalizowany bundle

    Dzięki namespaces, przeszliśmy z tej struktury:

    bash
    Kopiuj kod

    Skopiuj kod do schowka

    locale/├── en.json├── fr.json└── es.json

    Do takiej:

    bash
    Kopiuj kod

    Skopiuj kod do schowka

    locale/├── en/│   ├── common.json│   ├── navbar.json│   ├── footer.json│   ├── home.json│   └── about.json├── fr/│   └── ...└── es/    └── ...

    Teraz musisz precyzyjnie zarządzać, która część zawartości Twojej aplikacji powinna być ładowana i gdzie. W praktyce zdecydowana większość projektów pomija ten etap ze względu na jego złożoność (zobacz na przykład przewodnik next-i18next, aby zobaczyć wyzwania, jakie stanowi (tylko) przestrzeganie dobrych praktyk). W konsekwencji te projekty kończą z opisanym wcześniej problemem masywnego ładowania plików JSON.

    Należy zauważyć, że problem ten nie dotyczy tylko i18next, lecz wszystkich scentralizowanych podejść wymienionych powyżej.

    Jednak chcę przypomnieć, że nie wszystkie podejścia granularne to rozwiązują. Na przykład podejścia vue-i18n SFC czy inlang nie realizują domyślnie lazy loadingu tłumaczeń per-locale, więc po prostu zamieniasz problem wielkości bundla na inny.

    Co więcej, bez odpowiedniego separation of concerns znacznie trudniej jest wyodrębnić tłumaczenia i udostępnić je tłumaczom do przeglądu.

    Jak podejście per-component w Intlayer rozwiązuje ten problem

    Intlayer działa w kilku etapach:

    1. Deklaracja: Zadeklaruj swoją zawartość w dowolnym miejscu kodu, używając plików *.content.{ts|jsx|cjs|json|json5|...}. Zapewnia to separation of concerns przy jednoczesnym utrzymaniu zawartości zlokalizowanej obok kodu. Plik zawartości może być per-locale lub wielojęzyczny.
    2. Przetwarzanie: Intlayer uruchamia krok builda, aby przetworzyć logikę JS, obsłużyć brakujące tłumaczenia (fallbacki), wygenerować typy TypeScript, zarządzać zduplikowaną zawartością, pobierać treści z Twojego CMS i inne.
    3. Czyszczenie (Purging): Gdy Twoja aplikacja się buduje, Intlayer usuwa nieużywaną zawartość (trochę jak Tailwind zarządza klasami) poprzez zastąpienie zawartości w następujący sposób:

    Deklaracja:

    tsx
    Kopiuj kod

    Skopiuj kod do schowka

    // src/MyComponent.tsxexport const MyComponent = () => {  const content = useIntlayer("my-key");  return <h1>{content.title}</h1>;};
    tsx
    Kopiuj kod

    Skopiuj kod do schowka

    // src/myComponent.content.tsexport const {  key: "my-key",  content: t({    en: { title: "My title" },    fr: { title: "Mon titre" }  })}

    Przetwarzanie: Intlayer buduje słownik na podstawie pliku .content i generuje:

    json5
    Kopiuj kod

    Skopiuj kod do schowka

    // .intlayer/dynamic_dictionary/pl/my-key.json{  "key": "my-key",  "content": { "title": "Mój tytuł" },}

    Zastąpienie: Intlayer przekształca twój komponent podczas budowania aplikacji.

    - Tryb importu statycznego:

    tsx
    Kopiuj kod

    Skopiuj kod do schowka

    // Reprezentacja komponentu w składni podobnej do JSXexport const MyComponent = () => {  const content = useDictionary({    key: "my-key",    content: {      nodeType: "translation",      translation: {        pl: { title: "Mój tytuł" },        en: { title: "My title" },        fr: { title: "Mon titre" },      },    },  });  return <h1>{content.title}</h1>;};

    - Tryb importu dynamicznego:

    tsx
    Kopiuj kod

    Skopiuj kod do schowka

    // Reprezentacja komponentu w składni podobnej do JSXexport const MyComponent = () => {  const content = useDictionaryAsync({    en: () =>      import(".intlayer/dynamic_dictionary/en/my-key.json", {        with: { type: "json" },      }).then((mod) => mod.default),    // Same for other languages  });  return <h1>{content.title}</h1>;};
    useDictionaryAsync używa mechanizmu podobnego do Suspense, aby ładować zlokalizowany JSON tylko wtedy, gdy jest to potrzebne.

    Kluczowe korzyści tego podejścia per-component:

    • Utrzymywanie deklaracji treści blisko komponentów umożliwia lepszą konserwowalność (np. przeniesienie komponentu do innej aplikacji lub design systemu. Usunięcie folderu komponentu usuwa także powiązaną treść, tak jak prawdopodobnie już robisz dla swoich .test i .stories)

    /// Podejście per-component zapobiega konieczności, by agenty AI musiały przeskakiwać przez wszystkie różne pliki. Traktuje wszystkie tłumaczenia w jednym miejscu, ograniczając złożoność zadania i liczbę używanych tokenów.

    Ograniczenia

    Oczywiście to podejście wiąże się z kompromisami:

    • Trudniej je połączyć z innymi systemami l10n i dodatkowymi narzędziami.
    • Możesz zostać zablokowany w konkretnym rozwiązaniu (co właściwie już ma miejsce przy każdym rozwiązaniu i18n ze względu na ich specyficzną składnię).

    Dlatego Intlayer stara się dostarczyć kompletny zestaw narzędzi do i18n (100% darmowy i OSS), w tym tłumaczenia AI przy użyciu własnego dostawcy AI i kluczy API. Intlayer dostarcza również narzędzia do synchronizacji plików JSON, działające jak formatery wiadomości ICU / vue-i18n / i18next, mapujące treść do ich specyficznych formatów.

    Co to jest internacjonalizacja (i18n)?
    Alt+→

    Na tej stronie

      Dyskusje są anonimowe i regularnie przeglądane w celu rozwiązania typowych problemów. Podziel się pomysłami na funkcje, opinią o dokumentacji lub czymkolwiek związanym z Intlayer, wykorzystujemy te informacje do kształtowania naszej mapy drogowej i ulepszania produktu.

      lib/└── features/    └── login/        ├── login_screen.dart        └── login_screen.i18n.dart  # <- Tłumaczenia znajdują się tutaj
      import 'package:i18n_extension/i18n_extension.dart';extension Localization on String {  static var _t = Translations.byText("en") +      {        "Hello": {          "en": "Hello",          "fr": "Bonjour",        },      };  String get i18n => localize(this, _t);}
      locale/├── en.json├── fr.json└── es.json
      locale/├── en/│   ├── common.json│   ├── navbar.json│   ├── footer.json│   ├── home.json│   └── about.json├── fr/│   └── ...└── es/    └── ...
      // src/MyComponent.tsxexport const MyComponent = () => {  const content = useIntlayer("my-key");  return <h1>{content.title}</h1>;};
      // src/myComponent.content.tsexport const {  key: "my-key",  content: t({    en: { title: "My title" },    fr: { title: "Mon titre" }  })}
      // .intlayer/dynamic_dictionary/pl/my-key.json{  "key": "my-key",  "content": { "title": "Mój tytuł" },}
      // Reprezentacja komponentu w składni podobnej do JSXexport const MyComponent = () => {  const content = useDictionary({    key: "my-key",    content: {      nodeType: "translation",      translation: {        pl: { title: "Mój tytuł" },        en: { title: "My title" },        fr: { title: "Mon titre" },      },    },  });  return <h1>{content.title}</h1>;};
      // Reprezentacja komponentu w składni podobnej do JSXexport const MyComponent = () => {  const content = useDictionaryAsync({    en: () =>      import(".intlayer/dynamic_dictionary/en/my-key.json", {        with: { type: "json" },      }).then((mod) => mod.default),    // Same for other languages  });  return <h1>{content.title}</h1>;};