Spieszysz się? Sprawdź placeholdery tłumaczenia od razu w przeglądarce: za darmo, bez rejestracji, nic nie jest wysyłane.
Skąd bierze się %
W aplikacji iOS zlokalizowany tekst ze zmienną staje się ciągiem formatującym. W SwiftUI dzieje się to niejawnie:
Text("\(count) items left") // key: "%lld items left"
Text("Welcome back, \(name)!") // key: "Welcome back, %@!"
String(localized: "Total: \(price) €") // price is a Double: "Total: %lf €"
Int daje %lld, String daje %@, Double daje %lf. W starszym API piszesz format sam: String(format: NSLocalizedString("items_left", comment: ""), count), a plik .strings zawiera %d lub %ld.
Tak czy inaczej tłumacz dostaje tekst ze znakami % w środku, i tu zaczynają się problemy.
Trzy sposoby na zepsucie tłumaczenia
Pominięcie specyfikatora. "Invitez à modifier cette liste" zamiast "Invitez %@ à modifier cette liste". Aplikacja działa dalej: argument jest po prostu ignorowany, a imię osoby znika z ekranu. To najczęstszy i najcichszy błąd.
Zmiana typu. Zastąpienie %lld przez %@ lub odwrotnie. Format odczytuje wtedy argument jako niewłaściwy typ: w najlepszym razie bezsensowna liczba, w najgorszym awaria, bo %@ oczekuje obiektu, a dostaje liczbę całkowitą.
Cicha zmiana kolejności. "%@ shared %@" w języku o innym szyku zdania zamienia się w zdanie, w którym dwa imiona są zamienione miejscami. Składnia jest w porządku, sens już nie.
Zmiana kolejności za pomocą pozycji
Żeby zmienić kolejność, ponumeruj argumenty: %1$@ to pierwszy argument, %2$@ drugi, niezależnie od miejsca w zdaniu.
/* source */ "%@ shared %@ with you" = "%1$@ shared %2$@ with you";
/* French */ "%@ shared %@ with you" = "%1$@ vous a partagé %2$@";
/* other order */ "%@ shared %@ with you" = "%2$@ : partagé par %1$@";
Jedna zasada do zapamiętania: w obrębie tekstu albo wszystko jest numerowane, albo nic. Mieszanie %1$@ i %@ to zachowanie niezdefiniowane.
Francuska pułapka: „20 % de remise”
Jeśli wydajesz aplikację po francusku, warto to znać. Francuska typografia stawia spację przed znakiem procentu: „20 % de remise” (20% zniżki). W ciągu formatującym to problem, bo % d (procent, spacja, d) to poprawny specyfikator: spacja to flaga printf („dopełnij liczby dodatnie spacją”), a d to konwersja liczby całkowitej. Zdanie
"Profitez de %lld % de remise"
zawiera więc dwa specyfikatory, %lld i % d, dla jednego argumentu. Drugi odczytuje wartość, która nie istnieje. Poprawna forma podwaja znak:
"Profitez de %lld %% de remise"
Dotyczy to tylko tekstów przechodzących przez format, czyli zawierających co najmniej jedną zmienną. Stały tekst „20 % de remise” bez argumentu wyświetla się tak, jak został zapisany. Ale tłumaczenia się zmieniają: w dniu, w którym ktoś doda zmienną do tego zdania, samotny % staje się błędem. Ta sama pułapka grozi w każdym języku, w którym ktoś napisze spację po znaku procentu.
Liczba mnoga
Dla „1 element / 3 elementy / 5 elementów” nie twórz kilku kluczy i if w kodzie. W String Catalogu klucz %lld items left dostaje warianty liczby mnogiej (one, other, a także few lub many zależnie od języka; polski potrzebuje one, few, many i other), każdy z własnym %lld. Gdy zdanie ma dwie liczby zmieniające się niezależnie, Xcode używa podstawień (%#@items@): to rzadszy przypadek, edytowany w Xcode.
Kontrola przed wydaniem
Podstawowa kontrola jest mechaniczna: w każdym tłumaczeniu lista specyfikatorów (typ i pozycja) musi odpowiadać źródłu. Tak działa QA w LocalePilot dla plików .strings i .xcstrings: rozpoznaje %@, %1$@, %d, %ld, %lld, %.2f, %u, %i i %s, traktuje %% jako tekst i wskazuje brakujący lub nadmiarowy specyfikator przed eksportem. Celowo pomija rzadkie konwersje, takie jak %e czy %x, właśnie po to, żeby „20%en” nie było brane za błąd. Przypadek „% d” opisany wyżej wymaga przeglądu przez człowieka: przeszukaj tłumaczenia pod kątem %, po którym następuje spacja.