Architektura internacjonalizacji dla aplikacji tworzonych w czacie: zdefiniuj stabilne klucze tekstowe, reguły pluralizacji i jeden workflow tłumaczeń spójny dla web i mobile.

Pierwsze, co się psuje, to nie kod. To słowa.
Aplikacje tworzone w czacie często zaczynają jako szybki prototyp: wpisujesz „Dodaj przycisk z napisem Zapisz”, interfejs się pojawia i idziesz dalej. Tygodnie później chcesz hiszpański i niemiecki i odkrywasz, że te „tymczasowe” etykiety są rozrzucone po ekranach, komponentach, e‑mailach i komunikatach błędów.
Zmiany treści zdarzają się też częściej niż zmiany kodu. Nazwy produktów się zmieniają, teksty prawne są aktualizowane, onboarding jest przepisywany, a wsparcie prosi o jaśniejsze komunikaty o błędach. Jeśli tekst żyje bezpośrednio w kodzie UI, każda drobna zmiana treści staje się ryzykownym wydaniem, i łatwo pominąć miejsca, gdzie ta sama idea jest sformułowana inaczej.
Oto wczesne symptomy, które sygnalizują, że narastasz dług tłumaczeniowy:
Realistyczny przykład: budujesz prosty CRM w Koder.ai. Aplikacja webowa mówi „Deal stage”, aplikacja mobilna „Pipeline step”, a toast błędu „Invalid status”. Nawet jeśli wszystkie trzy są przetłumaczone, użytkownicy odczują niespójność, bo pojęcia nie pasują do siebie.
„Spójne” nie znaczy „ten sam ciąg znaków wszędzie”. Oznacza to:
Gdy potraktujesz tekst jako dane produktu, a nie dekorację, dodawanie języków przestaje być popłochiem, a staje się rutynową częścią budowy.
Internationalization (i18n) to praca, którą wykonujesz, aby aplikacja mogła obsługiwać wiele języków bez przepisywania kodu. Localization (l10n) to konkretna treść dla danego języka i regionu, np. francuski (Kanada) z odpowiednimi słowami, formatami dat i tonem.
Prosty cel: każdy fragment tekstu widocznego dla użytkownika powinien być wybierany przez stabilny klucz, a nie wpisywany bezpośrednio w kodzie UI. Jeśli możesz zmienić zdanie bez otwierania komponentu React czy widgetu Flutter, jesteś na dobrej drodze. To sedno architektury i18n dla aplikacji tworzonych w czacie, gdzie łatwo przypadkowo wypuścić na stałe tekst wygenerowany podczas sesji czatu.
Tekst widoczny dla użytkownika jest szerszy, niż wiele zespołów myśli. Obejmuje przyciski, etykiety, błędy walidacji, puste stany, wskazówki w onboarding, powiadomienia push, e‑maile, eksporty PDF i każdą wiadomość, którą użytkownik może zobaczyć lub usłyszeć. Zazwyczaj nie obejmuje logów wewnętrznych, nazw kolumn w bazie, identyfikatorów zdarzeń analitycznych, feature flagów ani debugowych komunikatów tylko dla adminów.
Gdzie powinny być przechowywane tłumaczenia? W praktyce często i frontend i backend, z wyraźną granicą odpowiedzialności.
Błąd do uniknięcia to mieszanie odpowiedzialności. Jeśli backend zwraca w pełni napisane po angielsku zdania dla błędów UI, frontend nie będzie ich mógł dobrze zlokalizować. Lepszy wzorzec: backend zwraca kod błędu (i ewentualnie bezpieczne parametry), a klient mapuje ten kod na zlokalizowany komunikat.
Własność tekstu to decyzja produktowa, nie szczegół techniczny. Zdecyduj wcześnie, kto może zmieniać słowa i zatwierdzać ton.
Jeśli produkt ma własność kopi, traktuj tłumaczenia jak treść: wersjonuj je, przeglądaj i daj produktowi bezpieczny sposób zgłaszania zmian. Jeśli inżynieria ma własność kopi, ustal regułę: każdy nowy tekst UI musi mieć klucz i domyślne tłumaczenie, zanim trafi do wydania.
Przykład: jeśli w flow rejestracji masz „Create account” w trzech różnych ekranach, zrób z tego jeden klucz używany wszędzie. To utrzymuje spójne znaczenie, przyspiesza tłumaczy i zapobiega temu, że drobne zmiany w słownictwie przeistoczą się w porządkowanie wielu ekranów później.
Klucze są kontraktem między twoim UI a tłumaczeniami. Jeśli ten kontrakt ciągle się zmienia, dostaniesz brakujące teksty, szybkie poprawki i niespójne słownictwo między web i mobile. Dobra architektura i18n dla aplikacji tworzonych w czacie zaczyna się od jednej zasady: klucze powinny opisywać znaczenie, a nie aktualne angielskie zdanie.
Używaj stabilnych identyfikatorów jako kluczy (np. billing.invoice.payNow) zamiast pełnej treści (np. "Pay now"). Klucze‑zdania łamią się w momencie, gdy ktoś zmieni słownictwo, doda interpunkcję lub zmieni wielkość liter.
Praktyczny wzorzec, który pozostaje czytelny: ekran (lub domena) + komponent + intencja. Trzymaj się nudnego i przewidywalnego formatu.
Przykłady:
auth.login.titleauth.login.emailLabelbilling.checkout.payButtonnav.settingserrors.network.offlineDecyduj, kiedy ponownie użyć klucza, a kiedy stworzyć nowy, pytając: „Czy znaczenie jest identyczne w każdym miejscu?” Ponownie używaj kluczy dla naprawdę ogólnych akcji, ale dziel klucze, gdy kontekst się zmienia. Na przykład „Save” w ekranie profilu może być prostą akcją, podczas gdy „Save” w złożonym edytorze może wymagać bardziej specyficznego tonu w niektórych językach.
Trzymaj wspólne teksty UI w dedykowanych przestrzeniach nazw, żeby nie powielać ich na różnych ekranach. Przykładowe koszyki, które dobrze działają:
common.actions.* (save, cancel, delete)common.status.* (loading, success)common.fields.* (search, password)errors.* (validation, network)nav.* (tabs, menu items)Gdy sformułowanie się zmienia, ale znaczenie pozostaje to samo, zachowaj klucz i zaktualizuj jedynie wartości tłumaczeń. O to chodzi w stabilnych identyfikatorach. Jeśli znaczenie się zmienia (nawet subtelnie), stwórz nowy klucz i zostaw stary na miejscu, dopóki nie potwierdzisz, że nikt go nie używa. To zapobiega „cichym” niezgodnościom, gdy stare tłumaczenie formalnie istnieje, ale jest teraz niepoprawne.
Mały przykład w stylu Koder.ai: twój czat generuje aplikację React i aplikację Flutter. Jeśli obie używają common.actions.save, masz spójne tłumaczenia wszędzie. Ale jeśli web używa profile.save, a mobile account.saveButton, z czasem się rozjedziecie, nawet jeśli angielski dziś wygląda identycznie.
Traktuj język źródłowy (często angielski) jako pojedyncze źródło prawdy. Trzymaj go w jednym miejscu, przeglądaj go jak kod i unikaj sytuacji, w której teksty pojawiają się w losowych komponentach „na chwilę”. To najszybszy sposób, by uniknąć na stałe wpisanego tekstu w UI i późniejszego przepisywania.
Prosta zasada: aplikacja może wyświetlać tekst tylko z systemu i18n. Jeśli ktoś potrzebuje nowej treści, dodaje najpierw klucz i domyślny komunikat, a potem używa tego klucza w UI. To utrzymuje architekturę i18n stabilną nawet wtedy, gdy funkcje się przemieszczają.
Jeśli wysyłasz zarówno web, jak i mobile, chcesz jeden wspólny katalog kluczy oraz miejsce, gdzie zespoły funkcyjne mogą pracować niezależnie. Praktyczny układ:
Trzymaj klucze identyczne na wszystkich platformach, nawet jeśli implementacja się różni (React na web, Flutter na mobile). Jeśli używasz platformy jak Koder.ai do generowania obu aplikacji z czatu, eksport kodu jest łatwiejszy, gdy oba projekty wskazują na te same nazwy kluczy i format wiadomości.
Tłumaczenia zmieniają się w czasie. Traktuj zmiany jak zmiany produktowe: małe, przeglądane i śledzone. Dobre review skupia się na znaczeniu i ponownym użyciu, a nie tylko na ortografii.
Aby powstrzymać dryf kluczy między zespołami, przypisz klucze do funkcji (billing., auth.) i nigdy nie zmieniaj nazw kluczy tylko dlatego, że zmieniło się słownictwo. Aktualizuj komunikat, zachowaj klucz. Klucze są identyfikatorami, nie kopią.
Zasady mnogiej formy różnią się między językami, więc prosty angielski schemat (1 vs wszystko inne) szybko zawodzi. Niektóre języki mają osobne formy dla 0, 1, 2–4 i wielu innych. Inne zmieniają całe zdanie, nie tylko rzeczownik. Jeśli wgrasz logikę pluralizacji w UI za pomocą if‑ów, skończysz z duplikowaniem tekstów i pominiętymi przypadkami.
Bezpieczniejsze podejście: trzymaj jedną elastyczną wiadomość na pomysł i pozwól warstwie i18n dobrać właściwą formę. Komunikaty w stylu ICU są do tego stworzone. Przenoszą decyzje gramatyczne do tłumaczenia, a nie do komponentów.
Oto mały przykład, który obejmuje przypadki, o których ludzie zapominają:
itemsCount = "{count, plural, =0 {No items} one {# item} other {# items}}"
Ten pojedynczy klucz obejmuje 0, 1 i wszystko inne. Tłumacze mogą zastąpić go właściwymi formami mnogimi dla swojego języka bez twojej ingerencji.
Gdy potrzebujesz sformułowań zależnych od płci lub roli, unikaj tworzenia oddzielnych kluczy jak welcome_male i welcome_female, chyba że produkt tego naprawdę wymaga. Użyj select, żeby zdanie pozostało jedną całością:
welcomeUser = "{gender, select, female {Welcome, Ms. {name}} male {Welcome, Mr. {name}} other {Welcome, {name}}}"
Aby uniknąć utknięcia w pułapce przypadków gramatycznych, utrzymuj zdania jak najbardziej kompletne. Nie sklejaj fragmentów jak "{count} " + t('items'), ponieważ wiele języków nie pozwala na taką zamianę kolejności wyrazów. Wol preferuj jedną wiadomość, która zawiera liczbę, rzeczownik i otaczające słowa.
Prosta zasada, która dobrze działa w aplikacjach tworzonych w czacie (w tym projektach Koder.ai): jeśli zdanie zawiera liczbę, osobę lub status, zrób z niego komunikat ICU od pierwszego dnia. Kosztuje to trochę więcej na początku, ale oszczędza dużo długu tłumaczeniowego później.
Jeśli twoja aplikacja React i aplikacja Flutter trzymają osobne pliki tłumaczeń, będą się z czasem rozjeżdżać. Ten sam przycisk dostanie inne słowo, klucz będzie miał różne znaczenia na web i mobile, a zgłoszenia do supportu zaczną mówić „aplikacja mówi X, a strona Y”.
Najprostsza naprawa jest też najważniejsza: wybierz jedno źródło prawdy i traktuj je jak kod. Dla większości zespołów oznacza to jedną wspólną serię plików locale (np. JSON z komunikatami w stylu ICU), które konsumują zarówno web, jak i mobile. Gdy budujesz aplikacje przez czat i generatory, to ma dodatkowe znaczenie, bo łatwo przypadkowo stworzyć nowy tekst w dwóch miejscach.
Praktyczne ustawienie to mały „pakiet i18n” lub folder zawierający:
React i Flutter stają się wtedy konsumentami. Nie powinny wymyślać nowych kluczy lokalnie. W workflowie w stylu Koder.ai możesz generować oba klienty z tego samego zestawu kluczy i trzymać zmiany pod review jak każdy inny update kodu.
Wyrównanie backendu to część tej samej historii. Błędy, powiadomienia i e‑maile nie powinny być ręcznie pisanymi angielskimi ciągami w Go. Zamiast tego zwracaj stabilne kody błędów (np. auth.invalid_password) plus bezpieczne parametry. Klienci mapują kody na przetłumaczony tekst. Dla maili wysyłanych z serwera serwer może renderować szablony używając tych samych kluczy i plików locale.
Stwórz małą księgę reguł i egzekwuj ją w code review:
Aby zapobiec duplikatom kluczy o różnych znaczeniach, dodaj pole „description” (lub plik komentarzy) dla tłumaczy i przyszłego siebie. Przykład: billing.trial_days_left powinien wyjaśnić, czy jest pokazywany jako banner, e‑mail, czy w obu miejscach. To jedno zdanie często zatrzymuje „wystarczająco podobne” ponowne użycia, które tworzą dług tłumaczeniowy.
Ta spójność to kręgosłup architektury i18n dla aplikacji tworzonych w czacie: jedno wspólne słownictwo, wiele powierzchni i brak niespodzianek przy wypuszczaniu kolejnego języka.
Dobra architektura i18n dla aplikacji tworzonych w czacie zaczyna się prosto: jeden zestaw kluczy wiadomości, jedno źródło prawdy dla kopii i te same zasady na web i mobile. Jeśli budujesz szybko (np. z Koder.ai), ta struktura pozwala zachować tempo bez generowania długu tłumaczeniowego.
Wybierz locale wcześnie i ustal, co się dzieje, gdy tłumaczenie braknie. Popularny wybór: pokaż preferowany język użytkownika, jeśli dostępny; w przeciwnym razie użyj angielskiego; loguj brakujące klucze, żeby naprawić je przed kolejnym wydaniem.
Następnie wprowadź to w życie:
billing.plan_name.pro lub auth.error.invalid_password. Trzymaj te same klucze wszędzie.t("key") w komponentach. W Flutter użyj wrappera lokalizacyjnego i wywołuj ten sam lookup oparty na kluczu w widgetach. Cel to te same klucze, nie ta sama biblioteka.{count, plural, one {# file} other {# files}} i Hello, {name}. To unika obejść typu if (count === 1) rozrzuconych po ekranach.Na koniec przetestuj jeden język z dłuższymi słowami (niemiecki) i jeden z inną interpunkcją. To szybko ujawni przyciski, które wystają, nagłówki, które źle się łamią, i układy zakładające długość angielskich słów.
Jeśli trzymasz tłumaczenia we wspólnym folderze (lub generowanym pakiecie) i traktujesz zmiany kopii jak zmiany w kodzie, twoje aplikacje web i mobile pozostaną spójne, nawet gdy funkcje powstają szybko w czacie.
Przetłumaczone ciągi UI to tylko połowa problemu. Większość aplikacji pokazuje też zmienne wartości jak daty, ceny, liczniki i nazwy. Jeśli potraktujesz te wartości jako zwykły tekst, dostaniesz dziwne formaty, złe strefy czasowe i zdania, które brzmią „nie tak” w wielu językach.
Zacznij od formatowania liczb, waluty i dat zgodnie z regułami locale, a nie własnym kodem. Użytkownik we Francji spodziewa się „1 234,50 €”, a w USA „$1,234.50”. To samo dotyczy dat: „03/04/2026” jest niejednoznaczne, ale formatowanie locale usuwa wątpliwości.
Strefy czasowe to kolejna pułapka. Serwery powinny przechowywać znaczki czasu w neutralnej formie (zwykle UTC), ale użytkownicy oczekują widoku w swojej strefie. Na przykład: zamówienie utworzone o 23:30 UTC może być „jutro” dla osoby w Tokio. Ustal jedną zasadę na ekran: pokazuj czas lokalny użytkownika dla wydarzeń osobistych, a stałą strefę biznesową dla np. okien odbioru w sklepie (i oznacz to wyraźnie).
Unikaj budowania zdań przez łączenie przetłumaczonych fragmentów. To łamie gramatykę, bo kolejność słów zmienia się między językami. Zamiast:
"{count} " + t("items") + " " + t("in_cart")
użyj jednej wiadomości z placeholderami, np. "{count} items in your cart". Tłumacz może wtedy bezpiecznie zmienić kolejność słów.
RTL to nie tylko kierunek tekstu. Przepływ układu się odwraca, niektóre ikony trzeba zmirrorować (np. strzałki wstecz), a mieszana zawartość (arabski + angielski kod produktu) może renderować w zaskakującej kolejności. Testuj realne ekrany, nie tylko pojedyncze etykiety, i upewnij się, że komponenty UI wspierają zmianę kierunku.
Nigdy nie tłumacz tego, co napisał użytkownik (imiona, adresy, zgłoszenia do supportu, wiadomości czatu). Możesz przetłumaczyć etykiety wokół nich i sformatować metadane (daty, liczby), ale sama treść musi pozostać bez zmian. Jeśli dodasz autotranslację później, niech to będzie jawna funkcja z przełącznikiem „oryginał/przekład”.
Praktyczny przykład: aplikacja zbudowana w Koder.ai może pokazywać „{name} renewed on {date} for {amount}”. Trzymaj to jako jedną wiadomość, formatuj {date} i {amount} według locale i wyświetlaj w strefie czasowej użytkownika. Ten wzorzec zapobiega wielu problemom z tłumaczeniami.
Szybkie reguły, które zwykle zapobiegają błędom:
Dług tłumaczeniowy zwykle zaczyna się od „jeszcze jednego szybkiego stringa” i przeradza w tygodnie sprzątania. W projektach tworzonych w czacie może się to stać jeszcze szybciej, bo teksty UI są generowane wewnątrz komponentów, formularzy, a nawet komunikatów backendu.
Najdroższe do naprawy są problemy, które rozprzestrzeniają się po aplikacji i trudno je odnaleźć.
Wyobraź sobie aplikację React i Flutter, które pokazują banner rozliczeniowy: „You have 1 free credit left”. Ktoś zmienia tekst na web na „You have one credit remaining” i zostawia klucz jako całe zdanie. Mobile nadal używa starego klucza. Teraz masz dwa klucze dla jednej koncepcji i tłumacze widzą oba.
Lepszy wzorzec to stabilne klucze (np. billing.creditsRemaining) i pluralizacja w stylu ICU, żeby gramatyka była poprawna we wszystkich językach. Jeśli używasz narzędzia generującego UI jak Koder.ai, dodaj wczesną regułę: każdy tekst widoczny dla użytkownika wygenerowany w czacie powinien trafić do plików tłumaczeń, a nie do komponentów czy błędów serwera. Ten mały nawyk chroni twoją architekturę i18n, gdy projekt rośnie.
Gdy internacjonalizacja wydaje się chaotyczna, zwykle dlatego, że podstawowe zasady nie zostały spisane. Mała lista kontrolna i jeden konkretny przykład mogą utrzymać zespół (i przyszłego siebie) z daleka od długu tłumaczeniowego.
Oto szybka lista kontrolna na każdy nowy ekran:
billing.invoice.paidStatus, nie billing.greenLabel).Prosty przykład: wprowadzasz ekran rozliczeń w angielskim, hiszpańskim i japońskim. UI ma: „Invoice”, „Paid”, „Due in 3 days”, „1 payment method” / „2 payment methods” oraz sumę typu „$1,234.50”. Jeśli zbudujesz to zgodnie z architekturą i18n dla aplikacji tworzonych w czacie, zdefiniujesz klucze raz (wspólne dla web i mobile), i każdy język wypełni tylko wartości. „Due in {days} days” stanie się komunikatem ICU, a formatowanie pieniędzy pochodzi z formattera zależnego od locale, nie z twardych przecinków.
Wdrażaj obsługę języków funkcja po funkcji, a nie jako wielki refaktor:
Udokumentuj dwie rzeczy, żeby nowe funkcje pozostały spójne: reguły nazewnictwa kluczy (z przykładami) i „definition of done” dla tekstów (brak twardo wpisanego copy, ICU dla pluralizacji, formatowanie dat/liczb, dodane do wspólnych katalogów).
Kolejne kroki: jeśli budujesz w Koder.ai, użyj Planning Mode, aby zdefiniować ekrany i klucze przed wygenerowaniem UI. Potem używaj snapshotów i rollbacków, by bezpiecznie iterować nad kopiami i tłumaczeniami między web i mobile, bez ryzyka zepsutego wydania.