Pokračujeme s L20n.js

Lokalizační framework L20n.js od Mozilly není jen pouhý tupý dosazovatel textu. Umožňuje nám například ovlivňovat obsah překladu pomocí námi poskytnutých dat, můžeme psát různá makra, nebo třeba adaptovat překlad na základě operačního systému, času nebo velikosti obrazovky. Ukažme si, jak na to.

Minulý díl nás seznámil se základy L20n. Ukázali jsme si v něm jeho syntaxi, nasazení, dosazování variant do překladu či naprosté minimum, co můžeme ovlivnit programově. To zdaleka není všechno. My totiž můžeme například poslat kontextu data a na jejich základě se může změnit překlad. Můžeme psát makra, která nám přinesou i nějakou tu logiku do lokalizace. A mnoho dalšího. Tak se konečně pusťme do práce.

Varianty entit

Tuto vlastnost jsme si ukázali již minule, ale nebylo zmíněno pár zajímavostí. Ta první je, že se atributy entit mohou vnořovat, i když to asi nění nic překvapivého a převratného. Jen je třeba dávat si pozor na to, abyste ve všech úrovních definovali výchozí hodnotu.

<nested {
    *alpha : {
        *one : "Foo",
        two : "Bar"
    },
    beta : {
        *one : "Baz",
        two : "Quo"
    }
}>

Také jsme si řekli, že výchozí varianty můžeme určit pomocí square bracket syntaxe (entita['výchozí']). To se nám bude hodit, až budeme pracovat s proměnnými. I takovouto syntaxí umíme definovat výchozí hodnoty pro vnořené atributy, a sice takto:

<nested['alpha', 'one'] {
    alpha : {
        one : "Foo",
        two : "Bar"
    },
    beta : {
        one : "Baz",
        two : "Quo"
    }
}>

To by bylo na úvod takové doplnění předchozích znalostí. Nyní se pusťme do předvádění pro nás zcela nových funkcí.

Externí proměnné

Jako první si ukážeme, jak předat lokalizačnímu kontextu data a jak je zpracovat. Nejdříve mu je předáme. Mějme například údaj o počtu nepřečtených e-mailů v našem mailovém klientu. Toto číslo pošleme kontextu a ten ho tak může použít v překladu. Máme dvě možnosti, jak to udělat. První je použít metodu updateData(data). Náš argument rozšíří objekt dat, který už má kontext uložený, což je dobré vědět. Posílaný objekt se tedy nemusí stát jediným zdrojem proměnných. Pouze se přidá k ostatním datům, případně přepíše ta se stejným jménem. Použití je jednoduché:

var ctx = document.l10n;

ctx.updateData({
    inbox : {
        unseen : 3;
    }
});

Po proběhnutí tohoto kódu však žádnou změnu neuvidíte, což je podle mě škoda. Tímto pouze aktualizujeme vnitřní data, která pak L20n použije k dalšímu překladu. Ten si vynutíme například nám již známou metodou requestLocales(jazyk) nějak takhle:

var selectedLang;
ctx.requestLocales(selectedLang);

Teď už změna nastane. L20n prostě vše znovu přeloží. A to může být ve větším projektu relativně zdlouhavé. Ukažme si proto druhou možnost, jak poslat kontextu data, a sice metodou getSync(entita, data), případně jejím vylepšeným sourozencem getEntitySync(entita, data). Druhá jmenovaná vrací objekt s těmito vlastnostmi: řetězec s překladem dané entity, objekt atributů dané entity (o nich později), objekt použitých globálních proměnných při překladu (o nich také později), a použitý jazyk (může být jiný než nastavený, je to totiž první jazyk ve fallback chainu, ke kterému se našla požadovaná entita – fallback chain byl zmíněn v minulém díle). Metoda getSync(entita, data) vrací pouze řetězec s překladem. Použití:

var data = {
    inbox : {
        unseen : 3
    }
};

var value = ctx.getSync('unseen', data);
var node = document.querySelector('[data-l10n-id="unseen"]');
node.innerHTML = value;

Je důležité se zmínit o dvou věcech. Za prvé, data, která takto pošleme, si L20n nijak neuloží. Prostě je pouze použije k překladu dané entity. Pokud chcete posílaná data při překladu entity uložit, je lepší nejdříve data předat pomocí updateData(data) a pak si entitu přeložit pomocí getSync(entita), protože druhý parametr je nepovinný. Pokud ho neuvedeme, L20n použije ta data, která má uložená.

A ta druhá věc? Minule jsme si říkali o tom, že přestože do našich *.l20n souborů můžeme psát i HTML tagy, „nebezpečné“ atributy jako href, nebo onclick se v překladu neprojeví. Bohužel, tohle neplatí pro řetězec vrácený metodami getSync() a getEntitySync(). Tam tyto atributy můžeme najít. Příjde mi to trochu schizofrenní, ale možná jsem jen nepochopil nějaký záměr vývojářů frameworku.

Teď už jen krátká zmínka o tom, že je vhodné posílat výchozí hodnoty, jinak nám bude kontext házet chyby, že mu ty proměnné chybí. Výchozích hodnot docílíme prostým vložením script tagu, kterým řekneme, že máme data pro L20n:

<script type="application/l10n-data+json">
    {
        "inbox" : {
            "unseen" : 0
        }
    }
</script>

Nyní již přichází čas, kdy data využijeme v našem překladu. Minule jsme si ukázali dosazování entit pomocí {{entita}} syntaxe. To využijeme i pro proměnné. To, že jde o externí proměnnou, určíme znakem $. Pojďme tedy využít počet nepřečtených zpráv nějak takto:

<unseen "Máte {{$inbox.unseen}} nepřečtených zpráv.">

Velmi jednoduché. Na základě proměnných můžeme také volit variantu entity:

<tweetsLink[$gender] {
    masculine : "Přečtěte si jeho tweety",
    feminime : "Přečtěte si její tweety",
    *neutral : "Přečtěte si tweety"
}>

Tato entita nám vrátí variantu překladu na základě informací o uživateli, které mu předáme. Je dobré se také pojistit nějakou výchozí variantou, která se použije, když kontextu pošleme nevyhovující data. A jak bychom tuto entitu mohli použít v naší dynamické aplikaci, která třeba využívá nějaký šablonovací engine? Například takhle:

//kolekce uživatelů
var users = [
    {
        username : 'addyosmani',
        gender : 'masculine'
    },
    {
        username : 'sindresorhus',
        gender : 'masculine'
    },
    {
        username : 'snipeyhead',
        gender : 'feminime'
    }
];

for (var i = 0; i < users.length; i++) {
    users[i].tweetsLink = ctx.getSync('tweetsLink', users[i]);
}

//pseudo framework
template('users').render(users);
<!--a šablona může vypadat nějak takto-->
<section class="users">
    {{#each}}
    <h1>{{username}}</h1>
    <a href="tweets/{{username}}">{{tweetsLink}}</a>
    {{/each}}
</section>

Atributy

Atributy mají být používány k definování dodatečných informací o entitě. K čemu to? Zatímco entity jako takové mají reprezentovat nějaký text, který má být zobrazen, atributy jsou tu od toho, aby danou entitu nějak popsaly. Například můžeme o entitě říct, že je mužského rodu, a na základě toho pak upravit překlad, který danou entitu využívá. Nebo k entitě můžete přidat ještě klávesovou zkratku, kterou použije jiná entita ve svém překladu. Nebude to asi tak hojně využívaná funkce. Jak si to představuje dokumentace frameworku:

<follow "Sledovat"
    accesskey: "F"
>
<unfollow "Přestat sledovat"
    accesskey: "U"
>
<followHelp "Stiskněte Ctrl+{{follow::accesskey}} pro sledovaní uživatele">
<unfollowHelp "Stiskněte Ctrl+{{unfollow::['accesskey']}}, abyste přestali uživatele sledovat">

Jak vidíte, je možné použít buď syntaxi entita::atribut, nebo syntaxi entita::['atribut'], což se opět může hodit při použití proměnných. V příkladu se také objevuje klávesa „Ctrl“. Toto můžeme ještě vylepšit, pokud budeme vědět, který operační systém uživatel má. Protože je velice pravděpodobné, že majitel stroje s Mac OS X žádnou klávesu „Ctrl“ mít nebude. Právě pro takové uživatele bychom měli zobrazit klávesu „Cmd“. A hned vzápětí si ukážeme, jak na to.

Globální proměnné

Globální promměné jsou něco jako ty, které jsme si popsali před chvílí, s tím rozdílem, že je kontextu nepředáváme my, nýbrž přímo L20n. Proto se také mírně liší syntaxe, a sice dolar se promění v zavináč. Ve verzi 1.0 jsou tři globální proměnné: @hour, @os a @screen.width.px.

Hodina se hodí například pro vylepšený pozdrav, kdy bereme v úvahu části dne. Operační systém použijeme třeba právě u klávesových zkratek. A s šířkou obrazovky přináší L20n tzv. responzivní lokalizaci, kdy můžeme text měnit podle šířky displeje. Ovšem pozor, neměl by se měnit obsah, pouze forma. Uživatelé s mobilním zařízením by neměli být ochuzeni o nic, co vidí uživatel desktopu.

Jak jsem slíbil, ukážeme si, jak vylepšit příklad se sledováním uživatele. Využijeme tedy globální proměnnou @os, která ve verzi 1.0 může mít tři hodnoty: win, linux a mac.

<follow "Sledovat"
    shortcut[@os]: {
        mac : 'Command+F',
        *pc : 'Ctrl+F'
    }
>
<unfollow "Přestat sledovat"
    shortcut[@os]: {
        mac : 'Command+U',
        *pc : 'Ctrl+U'
    }
>
<followHelp "Stiskněte {{follow::shortcut}} pro sledovaní uživatele">
<unfollowHelp "Stiskněte {{unfollow::['shortcut']}}, abyste přestali uživatele sledovat">

Jak to, že zde používám vlastnost pc, když takovou hodnotu proměnná @os nikdy mít nemůže? Vysvětlení je jednoduché: je označena jako výchozí. Pokud tedy proměnná nenabude hodnoty mac, zvolí se výchozí, a tedy pc. Podobně můžeme použít varianty *nix/win nebo *paid/linux.

Makra

S L20n můžeme psát dokonce makra, což jsou znovupoužitelné kusy logiky. Jsou to takové „funkce“. Uvozovky tu však nejsou jen tak pro nic za nic. Makrům můžeme předávat parametry (jinak, by ani neměla smysl), ale jinak jsou naše možnosti značně omezené oproti klasickému programování (což je na druhou stranu správně). Máme k dispozici unární operátory (-, +, !), operátory aritmetických operací (+, -, *, /, %), operátory porovnávání (<, <=, >, >=, ==, !=), logické operátory (||, &&) a nakonec ternární operátor pro podmínky (podminka ? pravda : nepravda). Klasické použití makra najdeme u plurálů:

<plural($n) {
    $n == 0 ? "zero" : 
    $n == 1 ? "one" :
    $n > 1 && $n < 5 ? "few" : "many"
}>

Toto makro nám vrací pro češtinu vhodné hodnoty, kdy můžeme použít správné skloňování (1 člověk, 2 lidé, 5 lidí). Určitě si ještě ukážeme způsob použití:

<followers[plural($count)] {
    zero : "Nemáte žádného followera!",
    one : "Máte jednoho followera!",
    few : "Máte {{$count}} followery!",
    *many : "Máte {{$count}} followerů!"
}>

Jak vidíte, makra se volají jak klasické funkce v programovacích jazycích. V tomto případě makru plural předáváme proměnnou $count, která nám sdělí počet followerů. Výsledek je pak mnohem krásnější, než když by se nám zobrazilo hnusné „Máte 1 followerů“.

Lokální entity

Lokální entity zdůvodňuje dokumentace L20n tak, že každý jazyk může potřebovat něco jiného než ostatní (například pády nebo rody). Můžeme tedy některé entity označit jako lokální (privátní), protože jsou specifické pro daný jazyk a neměly by být vidět nikde jinde. Je to velmi podobný princip jako zapouzdření v objektově orientovaných programovacích jazycích. Prostě je z hlediska návrhu vhodné použít privátní atributy/metody. Totéž tedy platí i zde. Lokální entity určujeme podtržítkem před názvem:

<_pronoun[$gender, $case] {
    *masculine : {
        *nominative : "on",
        genitive : "jeho",
        dative : "jemu",
        accusative : "jeho",
        locative : "něm",
        instrumental : "ním"
    },
    feminime : {
        *nominative : "ona",
        genitive : "její",
        dative : "jí",
        accusative : "ji",
        locative : "ní",
        instrumental : "ní"
    }
}>

Ihned musím dodat, že tato entita není úplně správná a neprůstřelná. Myslím tím například to, že sedmý pád (instrumental) může nabývat také hodnot „jím“/“jí“, což záleží na kontextu. Ale jako příklad to určitě postačí. No a teď tu naší lokální entitu použijme:

<addFriend "Toto je {{_user[$gender]}} {{$username}}. Chcete se s {{_pronoun[$gender].instrumental}} spřátelit?">

Dokonale lokalizovaný kód, který se umí adaptovat podle pohlaví uživatele. Neukázal jsem, jak vypadá entita _user, ale myslím, že je naprosto triviální. Prostě podle pohlaví vrátí text „uživatel“ nebo „uživatelka“.

Zbytek JS API

Ná závěr si ukážeme zbytek JavaScriptového API, které nám kontext L20n nabízí. Metoda localize(entity, callback) naváže náš callback na kontext a ten se zavolá, když jsou všechny vyjmenované entity k dispozici nebo nastala změna v překladu.

Do našeho callbacku je předán objekt l10n, který obsahuje objekt entities, kde najdeme námi vyjmenované entity s hodnotami, které by nám vrátila metoda getEntitySync(). Dále máme v l10n vlastnost reason, kde najdeme fallback chain jazyků (locales) nebo vlastnost global (to, když se změní hodnota některé z globálních proměnných použitých v entitě).

A jako poslední zde máme metodu stop(). Ta ovšem není v tuhle chvíli zdokumentovaná a z jejího zavolání jsem nezjistil, k čemu by mohla sloužit. Zběžně jsem prolétnul zdrojový kód a připadá mi, že by měla být daná entita vyřazena z překladu, ale nevšiml jsem si, že by se něco stalo. Asi tato vlastnost zatím není dovedena do konce. Kdybyste někdo věděl, budu rád, když se o tom zmíníte v komentářích.

Přeměňme tohle povídání v kus kódu:

ctx.localize(['footer', 'copyright'], function(l10n) {
    var node = document.querySelector('[data-l10n-id=footer]');
    node.innerHTML = l10n.entities.footer.value;
});

Další metodou, kterou můžeme v objektu kontextu najít je registerLocales(výchozí, dostupné). Tato metoda udělá přesně to samé jako náš manifest soubor. Můžete tak registrovat jazyky dynamicky, potom, co je třeba AJAXem zjistíte ze serveru.

Následující v seznamu je metoda registerLocaleNegotiator(vyjednávač), která registruje funkci, kde sestavujete vlastní fallback chain. Nenapadá mě jediný důvod, proč byste mohli být s výchozím „vyjednavačem“ nespokojení, ale máte tu možnost použít svůj. Jen pro úplnost dodám, že do vašeho callbacku jsou poslány tyto argumenty: dostupné jazyky, jazyky preferované uživatelem, výchozí jazyk a callback pro asynchronního vyjednavače. Kontextu musíte vrátit (nebo poslat do callbacku) pole jazyků, které se stane fallback chainem.

Dále tu máme metodu addResource(entita). Ta do kontextu přidá novou entitu. Parametrem je řetězec. Syntaxe je stejná jako v *.l20n souborech.

ctx.addResource('<hello "Ahoj světe!">');

A jako poslední – metoda linkResource(uri), která L20n řekne, kde může najít další soubor s entitami. Můžeme jí předat řetězec, který je přímo URI adresou k souboru. Nebo předáme funkci, která dostane přes první parametr požadovaný jazyk a může tak cestu k souboru vygenerovat dynamicky. Stačí pak pouze vrátit řetězec s danou URI.

ctx.linkResource('../locales/shared.l20n');
//nebo
ctx.linkResource(function (locale) {
    return '../locales/' + locale + '/page.l20n';
});

Závěr

A to je vše, přátelé. Doufám, že jste vše pochopili správně. Myslím, že na L20n.js není nic vyloženě složitého. Nad některými věcmi je třeba se chvíli zamyslet, ale jinak nám nic nebrání v tom vytvořit úžasně lokalizovaný web.

Klidný, nekonfliktní a skromný člověk, vždy s úsměvem na tváři. Vývojář, co se snaží prorazit do velkého světa vývoje softwaru. V tuto chvíli se nejraději topí v bažinách JavaScriptu a dalších webových technologií. Miluje čerstvé nápady, liberální řešení a minimalismus.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Zatím nebyl přidán žádný komentář, buďte první!

Přidat komentář
Zdroj: https://www.zdrojak.cz/?p=11543