Internasjonalisering (kjærlig referert til som i18n) er en effektiv måte å gjøre strukturert semantisk markup til hodepineinduserende tagsuppe. Enda verre blir det når man skal internasjonalisere javascript.
La oss ta en titt på noen måter dette kan gjøres rent praktisk. Eller som oftest; upraktisk. Vi starter der.
Inline hele javascriptkoden
Jobben med å sette inn språksnutter gjøres av jsp-motoren. Det kan være fristende å la html og javascript leve side om side i samme dokument. Ikke gå i den fella. Javascript i egne filer gir oss
Render en separat javascriptfil
Okay, så legger vi javascripten i en egen fil. JSP-motoren kan fortsatt rendre koden. Alt som trenges er å endre filtypen til jsp og inkludere fila på vanlig måte.
<script src="/javascript.jsp"></script>
Problemet er at koden fortsatt må prosesseres før den er komplett. Får vi fjernet jsp-taggene fra javascriptkoden, så vil:
- koden bli renere
- minifisering blir enklere
- og vi kan bruke JSLint
Men uten at taggene skrives rett inn av templatemotoren, så må vi finne en annen kontaktflate mellom scriptet og i18n-systemet.
Legg språkinfo i skjulte html-elementer
En mulighet er at scriptet plukker opp språkinfoen den trenger fra skjulte elementer på siden. HTMLen blir ikke akkurat noe vakrere:
<input type="hidden" id="i18n_confirm_delete"
value="<i18n:output text="confirm.delete"/>"/>
<input type="hidden" id="i18n_no_undo"
value="<i18n:output text="no.undo"/>"/>
Men vi har blitt kvitt jsp-tagger fra javascripten:
var text = {
confirm_delete: document.getElementById('i18n_confirm_delete').value,
no_undo: document.getElementById('i18n_no_undo').value
};
Og dermed er det viktigste på plass.
Vi kan nå bruke verktøy for å minifisere koden, slå sammen filer for raskere nedlasting, og sjekke for uheldig kodestil med JSLint.
Men alt er ikke vel på vestre frontend.
Avhengigheten som skapes mellom HTMLen og javascriptkoden er ikke tydelig nok, og virker tilfeldig. Når noen andre snubler over HTMLen så er det ikke åpenbart hvor de skjulte inputfeltene brukes.
Det kan bøtes på med noen velvalgte kommentarer, men la oss ikke gå dit. Kode bør være selvdokumenterende.
Legg språkinfo i et globalt tilgjengelig javascript-objekt
Istedet kan vi bygge et objekt med språksnuttene til bruk i scriptet:
<script type="text/javascript">
var TEXT = {
confirm_delete: '<i18n:output text="confirm.delete"/>',
no_undo: '<i18n:output text="no.undo"/>'
}
</script>
<script src="/javascript/admin_panel.js"></script>
Vi får i18n-tags i noe javascriptkode, men begrenset kun til konfigurasjon. Resten av scriptet, der funksjonaliteten vår ligger, vil ligge i en egen fil og få alle fordelene av det.
Problemet med denne løsningen er at vi får en avhengighet fra javascriptfila ut i det globale navnerommet. Andre som bruker samme i18n-teknikk i en annen del av siden kan lett komme til å overskrive vårt globale TEXT-objekt.
Men vi nærmer oss.
Snu på avhengigheten
I stedet for at scriptet henter språkinfoen den trenger fra DOM-elementer eller det globale navnerommet, så setter vi den inn ved initalisering.
<script src="/javascript/article_admin.js"></script>
<script type="text/javascript">
KM.article_admin.initialize({
text: {
confirm_delete: '<i18n:output text="confirm.delete"/>',
no_undo: '<i18n:output text="no.undo"/>'
}
});
</script>
All funksjonalitet som ikke har direkte med språksnutter å gjøre ligger i article_admin.js. I stedet for at vi setter opp progressive enhancement idet scriptet kjører, så flytter vi dette til en initialiseringsfunksjon som kjøres rett etterpå - med språksnuttene som trengs.
Rammeverket i article_admin.js for å motta tekstsnuttene ser slik ut:
(function () {
var text;
var delete_article = function () {
/* kode her har tilgang til text-objektet */
};
KM.article_admin = {
initialize: function (data) {
text = data.text;
}
};
}());
Legg merke til at vi definerer en anonym funksjon, slik at vi ikke forsøpler det globale navnerommet. Deretter binder vi text til hele funksjonsscopet, som settes i initialize.
Nå har vi separert den funksjonelle javascripten i egne filer, og samtidig samlet den i18n-spesifike koden på en plass.
Resultatet
Den endelige article_admin-koden kan se slik ut (med jQuery):
/*global KM, jQuery, confirm */
(function ($) {
var text,
delete_article = function ($article) {
$.post("/admin/delete_article",
{id: $article.attr("id")},
function () {
$article.fadeOut();
}
);
},
confirm_to_delete_article = function (event) {
var $article = $(this).closest(".article"),
name = $article.find("h3.name").text();
if (confirm(text.confirm_delete + name + text.no_undo)) {
delete_article($article);
}
return false;
},
initialize = function (data) {
text = data.text;
$("#articles a.delete").live("click", confirm_to_delete_article);
};
KM.article_admin = {initialize: initialize};
}(jQuery));