%@, %1$@, %lld: specyfikatory formatu, które psują się w tłumaczeniu

Zaktualizowano · LocalePilot

Tekst iOS ze zmienną zawiera %@ albo %lld. Tłumacz musi go zachować, we właściwym miejscu i z właściwym typem. Poniżej błędy, które ciągle wracają (w tym jeden typowy dla francuskiego), i sposób na ich wychwycenie.

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.

Najczęściej zadawane pytania

Czym różnią się %d, %ld i %lld?

Rozmiarem oczekiwanej liczby całkowitej. SwiftUI używa %lld dla interpolowanego Int; w ręcznie pisanym pliku .strings zachowaj specyfikator ze źródła.

Jak zapisać znak % w tłumaczonym tekście?

W tekście ze zmiennymi podwój go: %%. W przeciwnym razie „% d” lub „% s” mogą zostać odczytane jako specyfikatory.

Czy tłumacz może zmienić kolejność zmiennych?

Tak, za pomocą pozycji: %2$@, potem %1$@. Ale wtedy cały tekst musi być numerowany.