Internacjonalizacja aplikacji niesie za sobą wiele wyzwań – różne języki w różny sposób formatują daty, liczby czy czas relatywny, są to rzeczy bardzo kontekstowe które nie zawsze da się rozwiązać z poziomu tłumaczeń np. z plików JSON. Wyświetlanie nazw państw, regionów czy jednostek administracyjnych również nastręcza problemów ponieważ jest tego bardzo dużo. Jest też potrzeba formatowania wyświetlania walut.
Choć trzymanie tłumaczeń plikach JSON nie zniknie zapewne nigdy, część przypadków da się rozwiązać natywnie – przy użyciu obiektu Intl. Obiekt ten zawiera metody zwracające obiekty które mogą tłumaczyć relatywny czas czy nazwy krajów w podanym języku, czym oszczędzą nam pracy przy tłumaczeniu aplikacji.
Sortowanie z uwzględnieniem znaków diaktrycznych
Intl.Collator
Problemem podczas sortowania alfabetycznie list jest to, że domyślne sortowanie nie uwzględnia specyficznych dla danego języka znaków diaktrycznych.
Najpierw tworzymy tablicę którą chcemy sortować. Następnie pod kliknięcie przycisku Sort podpinamy funkcję, która skopiuje główną tablicę, pobierze język dla którego chcemy tablicę posortować i posortuje elementy wg tego języka. Rezultat zostanie wpisany do odpowiedniego elementu w HTML.
Końcowy efekt przykładu prezentuje się następująco:
Przykład sortowania z użyciem Intl.CollatorPrzykład sortowania z użyciem Intl.Collator – rozwinięte podpowiedzi
Podpowiedzi z użyciem elementu Datalist
Użycie elementu Datalist który zawiera opcje wyboru pozwoli nam zaimplementować ciekawą funkcjonalność – będzie można wybrać element z listy która wyświetli się po kliknięciu w pole bądź wpisać ręcznie. Dodatkowo, lista ta będzie filtrowana znakami które już wpiszemy w polu.
Formatowanie dat
Intl.DateTimeFormat
Jeżeli chcielibyśmy sformatować datę w danym języku trzeba pamiętać o kilku kwestiach takich jak przetłumaczenie nazwy dnia tygodnia, miesiąca czy kolejność. Przykładowo w języku angielskim w odmianie brytyjskiej najpierw występuje dzień, potem miesiąc i rok. W odmianie amerykańskiej za to najpierw jest miesiąc, potem dzień i na końcu rok. Z pomocą przychodzi nam obiekt DateTimeFormat, który po utworzeniu udostępnia między innymi metody format i formatRange.
Przykład użycia Intl.DateTimeFormat
Do utworzenia daty użyjemy pola typu datetime-local, który umożliwia wybór zarówno daty jak i godziny. W przeglądarce Chrome prezentuje się on następująco:
UI który ten kod reprezentuje pozwoli nam wybrać datę wraz z godziną, ustawić język i po kliknięciu przycisku Transform date wyświetlić datę przetłumaczoną na wybrany język.
document.querySelector('#datetime-format-transform-date').addEventListener('click', () => {
const date = new Date(document.querySelector('#datetime-format-date').value);
const locale = document.querySelector('#datetime-format-locale').value;
const dateTimeFormat = new Intl.DateTimeFormat(locale, {dateStyle: 'full', timeStyle: 'short'});
const formatted = dateTimeFormat.format(date);
document.querySelector('#datetime-format-transform-date-result').innerHTML = formatted;
});
document.querySelector('#datetime-format-transform-date').addEventListener('click', () => {
const date = new Date(document.querySelector('#datetime-format-date').value);
const locale = document.querySelector('#datetime-format-locale').value;
const dateTimeFormat = new Intl.DateTimeFormat(locale, {dateStyle: 'full', timeStyle: 'short'});
const formatted = dateTimeFormat.format(date);
document.querySelector('#datetime-format-transform-date-result').innerHTML = formatted;
});
Kod ten, po kliknięciu przycisku Transform date pobiera datę i język. Następnie tworzy obiekt DateTimeFormat z opcjami dzięki którymi wyświetlimy pełną datę – zawierającą nazwę miesiąca i dnia tygodnia oraz godzinę w formie krótkiej, bez strefy czasowej. Dla tego przykładu uproszczona konfiguracja wystarczy, mnogość opcji dostępna przy tworzeniu obiektu pozwoli na ewentualną szerszą konfigurację.
Efekt działania kodu wygląda następująco:
Formatowanie daty dla amerykańskiego angielskiego
Sprawdźmy w jaki sposób przykład ten sformatuje datę dla brytyjskiej odmiany angielskiego:
Formatowanie daty dla brytyjskiego angielskiego
Tłumaczenie nazw
Intl.DisplayNames
Jeśli chodzi o tłumaczenie nazw krajów, języków i walut pomoże nam obiekt utworzony przez konstruktor DisplayNames. Dane do przykładu pobierzemy z API udostępnianego przez RestCountries z którego pobierzemy listę krajów członkowskich Unii Europejskiej – konkretnie spod adresu https://restcountries.eu/rest/v2/regionalbloc/eu. Z obiektów które przyjdą w odpowiedzi na żądanie weźmiemy tylko kod kraju, jego języki oraz waluty. Dodatkowo dane wpiszemy do localStorage, by nie nadużywać darmowego API.
Przykład użycia Intl.DisplayNames
Pomijając elementy które powtarzają się z poprzednich przykładów (pełny kod dostępny będzie na Github i na Github Pages w wersji live) czyli pole wyboru języka stwórzmy kod HTML który pozwoli wyświetlać dane krajów w tabelce:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<details>
<summary>Show EU countries with languages and currencies</summary>
<tableid="display-names-table">
<thead>
<tr>
<th>Country</th>
<th>Languages</th>
<th>Currencies</th>
</tr>
</thead>
<tbody>
<tr>
<tdcolspan="3">Click button to see translated data</td>
</tr>
</tbody>
</table>
</details>
<details>
<summary>Show EU countries with languages and currencies</summary>
<table id="display-names-table">
<thead>
<tr>
<th>Country</th>
<th>Languages</th>
<th>Currencies</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="3">Click button to see translated data</td>
</tr>
</tbody>
</table>
</details>
<details>
<summary>Show EU countries with languages and currencies</summary>
<table id="display-names-table">
<thead>
<tr>
<th>Country</th>
<th>Languages</th>
<th>Currencies</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="3">Click button to see translated data</td>
</tr>
</tbody>
</table>
</details>
Element details pozwoli nam stworzyć element w którym po kliknięciu jego zawartość się rozwinie i zobaczymy tabelkę z nazwą kraju, językami i walutami w nich używanymi.
Następnie przy pomocy metody of przetłumaczymy nazwę kraju, jego języki i waluty i przemapujemy eja wiersze tabeli. Pamiętać musimy by dla języków i walut użyć bloku try..catch, ponieważ nie możemy być pewni że API zawsze zwróci kody obsługiwane przez DisplayNames:
const locale = document.querySelector('#relative-time-locale').value;
const date = new Date(document.querySelector('#relative-time-date').value);
const current = new Date();
let value;
let unit;
if (date.getFullYear() !== current.getFullYear()) {
value = date.getFullYear() - current.getFullYear();
unit = 'year';
}
else if (date.getMonth() !== current.getMonth()) {
value = date.getMonth() - current.getMonth();
unit = 'month';
}
else if (date.getDate() !== current.getDate()) {
value = date.getDate() - current.getDate();
unit = 'day';
}
else if (date.getHours() !== current.getHours()) {
value = date.getHours() - current.getHours();
unit = 'hour';
}
else if (date.getMinutes() !== current.getMinutes()) {
value = date.getMinutes() - current.getMinutes();
unit = 'minute';
}
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto'});
document.querySelector('#relative-time-transform-result').innerHTML = formatter.format(value, unit);
const locale = document.querySelector('#relative-time-locale').value;
const date = new Date(document.querySelector('#relative-time-date').value);
const current = new Date();
let value;
let unit;
if (date.getFullYear() !== current.getFullYear()) {
value = date.getFullYear() - current.getFullYear();
unit = 'year';
}
else if (date.getMonth() !== current.getMonth()) {
value = date.getMonth() - current.getMonth();
unit = 'month';
}
else if (date.getDate() !== current.getDate()) {
value = date.getDate() - current.getDate();
unit = 'day';
}
else if (date.getHours() !== current.getHours()) {
value = date.getHours() - current.getHours();
unit = 'hour';
}
else if (date.getMinutes() !== current.getMinutes()) {
value = date.getMinutes() - current.getMinutes();
unit = 'minute';
}
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto'});
document.querySelector('#relative-time-transform-result').innerHTML = formatter.format(value, unit);
Choć Intl nie zastąpi klasycznej obsługi tłumaczeń, pomaga w kilku specyficznych przypadkach. Tematy typu formatowanie dat w danym języku czy względne daty potrafią być trudne w obsłudze i bardzo dobrze, że istnieją możliwości by obsłużyć te przypadki natywnie.