Link Search Menu Expand Document

Aquest document és una traducció lliure de PHP The Right Way

Introducció

En primer lloc, hem de definir els dos conceptes similars i altres coses relacionades:

  • La internacionalització és quan organitzeu el vostre codi, de manera que es pot adaptar a diferents idiomes o regions sense que es facin refactualitzacions. Aquesta acció sol realitzar-se un cop, preferiblement, al començament del projecte, o bé, probablement haureu de fer alguns canvis immensos en l’origen.

  • La localització es produeix quan adapteu la interfície (principalment) mitjançant la traducció de continguts, basada en el treball i18n realitzat abans. Normalment es fa cada vegada que un nou idioma o regió necessita suport i s’actualitza quan s’afegeixen noves peces d’interfície, ja que han d’estar disponibles en tots els idiomes compatibles.

  • La pluralització defineix les regles necessàries entre diferents idiomes per interoperar cadenes que contenen números i comptadors. Per exemple, en anglès, quan teniu un únic ítem, és singular, i qualsevol cosa diferent a això s’anomena plural; En aquest idioma, el plural s’indica afegint una S després d’algunes paraules i, de vegades, en canvia parts. En altres idiomes, com el rus o el serbi, hi ha dues formes plurals a més del singular, fins i tot es poden trobar llengües amb un total de quatre, cinc o sis formes, com ara l’eslovè, l’irlandès o l’àrab.

Formes comunes d’implementar

La manera més fàcil d’internacionalitzar el programari PHP és mitjançant fitxers de matrius i utilitzant aquestes cadenes en plantilles, com <h1><?=$TRANS['title_about_page']?> </h1>. Aquesta manera, però, difícilment es recomana per a projectes seriosos, ja que planteja alguns problemes de manteniment al llarg de la vida del projecte. Alguns podrien aparèixer al principi, com ara la pluralització. Per tant, no ho proveu si el vostre projecte contindrà més d’un parell de pàgines.

La forma més clàssica i que sovint es considera com a referència per a i18n i l10n és una eina Unix anomenada gettext. Data de 1995 i continua sent una aplicació completa per traduir programari.

Altres eines

Hi ha biblioteques comunes utilitzades que admeten Gettext i altres implementacions d’i18n. Alguns poden semblar més fàcils d’instal·lar o practicar funcions addicionals o formats de fitxers i18n. En aquest document, ens centrem en les eines que es proporcionen amb el nucli de PHP, però aquí enumerem altres per complementar-les:

  • aura/intl: proporciona eines d’internacionalització (I18N), específicament traducció de missatges per localització orientada a paquets. Utilitza formats de matrius per a missatges. No proporciona un extractor de missatges, però proporciona un format avançat de missatges mitjançant l’extensió intl (inclosos els missatges pluralitzats).
  • oscarotero/Gettext: suport de Gettext amb una interfície OO; inclou funcions auxiliars millorades, potents extractors per a diversos formats d’arxius (alguns no suportats nativament per l’ordre gettext) i també poden exportar-los a altres formats a més dels fitxers .mo/.po. Pot ser útil si necessiteu integrar els vostres fitxers de traducció a altres parts del sistema, com una interfície de JavaScript.
  • symfony/translate: admet molts formats diferents, però recomana utilitzar els formats XLIFF de verbose No inclou funcions auxiliars ni un extractor integrat, però admet els marcadors de posició que utilitzin strtr() internament.
  • zend/i18n: admet matrius matrius i fitxers INI, o format Gettext. Implementa una capa de memòria cau que us permetrà evitar la lectura del sistema de fitxers cada vegada. També inclou ajudants de visualització i filtres d’entrada i validadors. Tot i això, no té cap extractor de missatges.

Altres frameworks també inclouen mòduls i18n, però aquests no estan disponibles fora dels seus codis de bases:

  • Laravel admet fitxers de matriu bàsics, no té extractor automàtic, però inclou un ajudant @lang per a fitxers de plantilles.
  • Yii admet traduccions basades en matrius, Gettext i basades en bases de dades i inclou un extractor de missatges. Està avalat per l’extensió Intl, disponible des de PHP 5.3, i basat en el projecte de la UCI; això permet a Yii executar reemplaçaments potents, com ara escriure números, formatar dates, hores, intervals, moneda i ordinals.

Si decidiu anar a una de les biblioteques que no ofereixen cap extractor, potser voldreu utilitzar els formats de text, de manera que podeu utilitzar la cadena d’eines gettext original (inclosa Poedit) tal i com es descriu a la resta del capítol.

Gettext

Instal·lació

És possible que hàgiu d’instal·lar Gettext i la biblioteca PHP relacionada mitjançant el gestor de paquets, com apt-get o yum. Després d’instal·lar-lo, activeu-lo afegint extension=gettext.so (Linux/Unix) o extension=php_gettext.dll (Windows) al vostre php.ini.

Aquí també utilitzarem Poedit per crear fitxers de traducció. Probablement el trobareu al gestor de paquets del vostre sistema; està disponible per a Unix, Mac i Windows, i també es pot descarregar gratuïtament al seu lloc web.

Estructura

Tipus d’arxius

Hi ha tres fitxers amb els que acostumes a treballar mentre uses gettext. Els principals són els fitxers PO (Objecte portàtil) i MO (Objecte màquina), el primer essent una llista de “objectes traduïts” llegibles i el segon, el binari corresponent que s’ha d’interpretar per gettext quan es fa una localització. També hi ha un fitxer POT (plantilla), que només conté totes les claus existents dels fitxers font i que es pot utilitzar com a guia per generar i actualitzar tots els fitxers PO. Aquests fitxers de plantilla no són obligatoris: depenent de l’eina que utilitzeu per fer l10n, podeu anar bé només amb fitxers PO/MO. Sempre tindreu un parell de fitxers PO/MO per idioma i regió, però només un POT per domini.

Dominis

Hi ha alguns casos, en grans projectes, en què potser haureu de separar les traduccions quan les mateixes paraules transmeten un significat diferent a un context. En aquests casos, els heu dividit en diferents dominis. Bàsicament són grups anomenats de fitxers POT/PO/MO, on el nom de fitxer és el domini de traducció. Els projectes de mida petita i mitjana solen, per senzillesa, utilitzar només un domini; el seu nom és arbitrari, però farem servir “principal” per a les nostres mostres de codi. En els projectes Symfony, per exemple, els dominis s’utilitzen per separar la traducció per missatges de validació.

Codi local

Una configuració regional és simplement un codi que identifica una versió d’un idioma. Es defineix seguint les especificacions ISO 639-1 i ISO 3166-1 alfa-2: dues lletres minúscules per a l’idioma, opcionalment seguides d’un subratllat i dues majúscules que identifiquen el país o el codi regional. Per a llengües rares, s’utilitzen tres lletres.

Per a alguns oradors, la part del país pot semblar redundant. De fet, algunes llengües tenen dialectes en diferents països, com l’alemany austríac (de_AT) o el portuguès brasiler (pt_BR). La segona part s’utilitza per distingir aquests dialectes: quan no està present, es pren com a versió “genèrica” ​​o “híbrida” de la llengua.

Estructura del directori

Per utilitzar Gettext, haurem de complir amb una estructura específica de carpetes. Primer, haureu de seleccionar una arrel arbitrària per als fitxers l10n del dipòsit d’origen. Al seu interior, tindreu una carpeta per a cada configuració regional necessària i una carpeta fixa LC_MESSAGES que contindrà tots els vostres parells de PO/MO. Exemple:

<arrel del projecte>
 ├─ src/
 ├─ plantilles/
 └─ locales/
    ├─ forum.pot
    ├─ site.pot
    ├─ de/
    │ └─ LC_MESSAGES/
    │ ├─ forum.mo
    │ ├─ forum.po
    │ ├─ site.mo
    │ └─ site.po
    ├─ es_ES/
    │ └─ LC_MESSAGES/
    │ └─ ...
    ├─ fr/
    │ └─ ...
    ├─ pt_BR/
    │ └─ ...
    └─ pt_PT/
       └─ ...

Les formes plurals

Com dèiem a la introducció, diferents idiomes podrien tindre diferents regles plurals. Tot i això, gettext ens torna a estalviar d’aquest problema. Quan creeu un fitxer .po nou, haureu de declarar les regles plurals d’aquest idioma i les peces traduïdes que siguin sensibles al plural tindran una forma diferent per a cadascuna d’aquestes regles. Quan crideu a Gettext en el codi, haureu d’especificar el número relacionat amb l’oració, i es treballarà la forma correcta a utilitzar, fins i tot si es necessita substitució de cadenes.

Les regles plurals inclouen el nombre de plurals disponibles i un test booleà amb n que definiria en quina regla cau el nombre donat (començant el recompte amb 0). Per exemple:

  • Japonès: nplurals=1; plural=0: només una regla
  • Anglès: nplurals=2; plural=(n != 1); - dues regles, la primera si N és una, la segona regla en cas contrari
  • Portuguès brasiler: nplurals = 2; plural=(n> 1); - dues regles, segona si N és més gran que una, primer si no.

Ara que heu entès les bases de com funcionen les regles plurals, i si no ho heu entés, consulteu una explicació més profunda al tutorial de LingoHub, potser necessitareu copiar les que vos facen falta en una llista en lloc d’escriure-les a mà.

Quan crideu gettext per localitzar frases amb comptadors, també heu de proporcionar el número relacionat. gettext esbrinarà quina regla hauria de ser efectiva i utilitzarà la versió localitzada correcta. Haureu d’incloure al fitxer .po una frase diferent per a cada regla plural definida.

Implementació de mostra

Després de tota aquesta teoria, anem a ser una mica pràctics. Aquí teniu un extracte d’un fitxer .po; no us importa el format, però el contingut general; més endavant, aprendreu a editar-lo fàcilment:

msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n>1);\n"

msgid "We are now translating some strings"
msgstr "Nós estamos traduzindo algumas strings agora"

msgid "Hello %1$s! Your last visit was on %2$s"
msgstr "Olá %1$s! Sua última visita foi em %2$s"

msgid "Only one unread message"
msgid_plural "%d unread messages"
msgstr[0] "Só uma mensagem não lida"
msgstr[1] "%d mensagens não lidas"

La primera secció funciona com una capçalera, tenint els paràmetres msgid i msgstr buits. Descriu la codificació del fitxer, les formes plurals i altres coses menys rellevants. La segona secció tradueix una cadena senzilla de l’anglès al portuguès brasiler i la tercera fa el mateix, però aprofitant la substitució de cadenes del sprintf, de manera que la traducció pot contenir el nom d’usuari i la data de visita. L’última secció és un exemple de formes de pluralització, que mostra la versió singular i plural tal com es defineix en anglès i les traduccions corresponents com a 0 i 1 (seguint el número donat per la regla del plural). Allà, s’utilitza també la substitució de cadenes, de manera que el nombre es pot veure directament a l’oració, utilitzant %d. Les formes plurals sempre tenen dos (singular i plural), per la qual cosa s’aconsella no utilitzar un llenguatge complex com a font de traducció.

Discussió sobre les claus l10n

Com haureu notat, estem utilitzant com a font font la frase real en anglès. Que el msgid és el mateix que s’utilitza a tots els vostres fitxers .po, és a dir, que altres idiomes tindran el mateix format i els mateixos camps, però línies traduïdes.

Aquí parlem de claus de traducció, hi ha dues escoles principals:

  1. msgstr com a frase real. Els avantatges principals són:
    • si hi ha peces del programari sense traduir en un llenguatge determinat, la clau mostrada encara mantindrà algun significat. Exemple: si us convé traduir de l’anglès al castellà però necessiteu ajuda per traduir al francès, podeu publicar la pàgina nova amb frases en francès que falten i, en canvi, algunes parts del lloc web es mostrarien en anglès;
    • és molt més fàcil per al traductor entendre el que falta i fer una traducció adequada basant-se en les característiques de configuració;
    • et dóna de série la traducció per un idioma: el de procedència;
    • L’únic desavantatge: si cal canviar el text real haureu de substituir el mateix msgid per diversos fitxers d’idiomes.
  2. msgstr com a clau única i estructurada. Descriuria el paper de la frase en l’aplicació de manera estructurada, inclosa la plantilla o la part on es troba la cadena en lloc del contingut.
    • és una manera fantàstica d’organitzar el codi, separant el contingut del text de la lògica de la plantilla.
    • tanmateix, això podria provocar problemes al traductor ja que faria falta el context. Caldria un fitxer d’idioma font com a base d’altres traduccions. Exemple: el desenvolupador idealment tindria un fitxer en.po, que els traductors llegirien per entendre què escriure en fr.po per exemple.
    • les traduccions que falten mostraran claus sense sentit a la pantalla (top_menu.welcome en lloc de Hello there, User! a la pàgina en francès sense traduir). És bo, ja que obligaria a que la traducció estiga completa abans de publicar-la, però els problemes de traducció són notablement horribles a la interfície. Algunes biblioteques, però, inclouen una opció per especificar un idioma determinat com a “fallback”, tenint un comportament similar a l’altre plantejament.

El manual de Gettext afavoreix el primer plantejament, ja que, en general, és més fàcil per a traductors i usuaris en cas de problemes. Així treballarem ací. Tanmateix, la documentació de Symfony afavoreix la traducció basada en paraules clau, per permetre canvis independents de totes les traduccions sense afectar també les plantilles.

Poedit

Ús habitual

En una aplicació típica, faríeu servir algunes funcions de Gettext mentre escriviu text estàtic a les vostres pàgines. Aquestes frases apareixerien en els fitxers .po, es traduiran, i es compilaran en fitxers .mo i, després, les utilitzarà Gettext per a mostrar-les a la interfície. Tenint en compte això, anem a demostrar el que hem comentat fins ara en un exemple pas a pas:

1. Un fitxer de plantilla d’exemple, incloent algunes crides de text complet

<?php include 'i18n_setup.php' ?>
<div id="header">
    <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1>
    <!-- code indented this way only for legibility -->
    <?php if ($unread): ?>
        <h2><?=sprintf(
            ngettext('Only one unread message',
                     '%d unread messages',
                     $unread),
            $unread)?>
        </h2>
    <?php endif ?>
</div>

<h1><?=gettext('Introduction')?></h1>
<p><?=gettext('We\'re now translating some strings')?></p>
  • gettext() simplement tradueix un msgid al seu corresponent msgstr per a un idioma determinat. També existeix la funció en format abreviat _() que funciona de la mateixa manera;
  • ngettext() fa el mateix però amb regles plurals;
  • també existeixen dgettext() i dngettext(), que permeten substituir el domini per a una determinada crida. Més informació sobre la configuració del domini en el següent exemple.

2. Un fitxer de configuració d’exemple, seleccionant la configuració local correcta i configurant Gettext

<?php
/**
 * Verifies if the given $locale is supported in the project
 * @param string $locale
 * @return bool
 */
function valid($locale) {
   return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es']);
}

//setting the source/default locale, for informational purposes
$lang = 'en_US';

if (isset($_GET['lang']) && valid($_GET['lang'])) {
    // the locale can be changed through the query-string
    $lang = $_GET['lang'];    //you should sanitize this!
    setcookie('lang', $lang); //it's stored in a cookie so it can be reused
} elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) {
    // if the cookie is present instead, let's just keep it
    $lang = $_COOKIE['lang']; //you should sanitize this!
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
    // default: look for the languages the browser says the user accepts
    $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
    array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ['-' => '_']); });
    foreach ($langs as $browser_lang) {
        if (valid($browser_lang)) {
            $lang = $browser_lang;
            break;
        }
    }
}

// here we define the global system locale given the found language
putenv("LANG=$lang");

// this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instance
setlocale(LC_ALL, $lang);

// this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mo
bindtextdomain('main', '../locales');

// indicates in what encoding the file should be read
bind_textdomain_codeset('main', 'UTF-8');

// if your application has additional domains, as cited before, you should bind them here as well
bindtextdomain('forum', '../locales');
bind_textdomain_codeset('forum', 'UTF-8');

// here we indicate the default domain the gettext() calls will respond to
textdomain('main');

// this would look for the string in forum.mo instead of main.mo
// echo dgettext('forum', 'Welcome back!');
?>

3. Preparació de la traducció per a la primera tirada

Un dels grans avantatges que té Gettext sobre els paquets personalitzats i18n és el seu extens i potent format de fitxer. “Oh home, que és molt difícil d’entendre i editar a mà, una matriu senzilla seria més fàcil!” No us equivoqueu, aplicacions com Poedit estan ací per ajudar-vos molt. Podeu obtenir el programa des del lloc web, que és gratuït i disponible per a totes les plataformes. És una eina senzilla i alhora molt potent: utilitzar totes les funcions de què disposa Gettext. Aquesta guia es basa en PoEdit 2.2.

En començar, haureu de seleccionar “Fitxer> Nou …” al menú. Se us demanarà un idioma base: ací podeu seleccionar/filtrar l’idioma que vulgueu traduir o utilitzar el format que hem esmentat abans, com ara en_US o pt_BR.

Ara, deseu el fitxer: també heu d’utilitzar l’estructura de directoris que hem esmentat abans. Aleshores, haureu de fer clic a “Extreu de fonts” i aquí configurareu diversos paràmetres per a les tasques d’extracció i traducció. Podreu trobar-ne més endavant a través de “Catàleg> Propietats”:

  • Rutes de la font: aquí heu d’incloure totes les carpetes del projecte on hem cridat gettext() (i similars): normalment és la vostra carpeta de plantilles/vistes. Aquest és l’únic paràmetre obligatori;
  • Propietats de la traducció:
    • Nom i versió del projecte, adreça de correu electrònic de l’equip de traducció: informació útil que es troba a la capçalera del fitxer .po;
    • Formes plurals: aquí es mostren les regles que hem esmentat abans; també hi ha un enllaç amb mostres. Podeu guardar-ho amb l’opció predeterminada la majoria de les vegades, ja que PoEdit ja inclou una base de dades útil de regles plurals per a molts idiomes.
    • Codificació de caracters: UTF-8, preferentment;
    • Característica del codi font: heu de configurar aquí el conjunt utilitzat per la base de codi, probablement també UTF-8.
    • Paraules clau d’origen: el programari subjacent sap com es veuen en diverses llengües de programació les funcions de gettext() i similars, però també podreu crear les vostres funcions de traducció. Aquí hi afegireu aquests altres mètodes. Això es tractarà més endavant a la secció “Consells”.

Després d’establir aquests punts, s’executarà una exploració a través dels fitxers font per trobar totes les crides de localització. Després de cada exploració, PoEdit mostrarà un resum del que s’ha trobat i del que s’ha tret dels fitxers d’origen. Les entrades noves s’enviaran buides a la taula de traducció i començareu a escriure les versions localitzades d’aquestes cadenes. Desa-ho i es recopilarà un fitxer .mo a la mateixa carpeta i ta-dah: el teu projecte s’ha internacionalitzat.

4. Traducció de cadenes

Com heu observat abans, hi ha dos tipus principals de cadenes localitzades: les simples i les amb formes plurals. Els primers tenen només dues caixes: font i cadena localitzada. La cadena font no es pot modificar, ja que Gettext/Poedit no inclouen els poders per modificar els fitxers d’origen; heu de canviar la font en si i canviar-los de nou. Consell: podeu fer clic amb el botó dret a la línia de traducció i us indicarà els fitxers i les línies d’origen on s’està utilitzant aquesta cadena. D’altra banda, les cadenes de formes plurals inclouen dos quadres per mostrar les dues cadenes d’origen i les pestanyes per poder configurar els diferents formularis finals.

Sempre que canvieu les fonts i actualitzeu les traduccions, només cal que feu clic a Actualitzar i Poedit canviarà el codi, eliminant les entrades inexistents, fusionant les que es van canviar i afegint-ne de noves. També pot intentar endevinar algunes traduccions, en funció d’altres que vau fer. Aquelles endevines i les entrades canviades rebran un marcador “difús”, que indica que necessita revisió, que apareixerà daurada a la llista. També és útil si teniu un equip de traducció i algú intenta escriure alguna cosa del qual no està segur: només marqueu Fuzzy i algú altre revisarà més endavant.

Finalment, s’aconsella deixar marcats “Veure > entrades no traduïdes”, ja que t’ajudarà molt a no oblidar cap entrada. Des d’aquest menú, també podeu obrir parts de la interfície d’interès que us permetran deixar informació contextual per als traductors, si cal.

Consells i trucs

Possibles problemes de memòria cau

Si utilitzeu PHP com a mòdul a Apache (mod_php), podríeu tenir problemes amb el fitxer .mo en memòria cau. El primer cop es llegeix i, per actualitzar-lo, potser haureu de reiniciar el servidor. A Nginx i PHP5 amb un parell de actualitzacions de pàgines per actualitzar la memòria cau de traducció és suficient, i en PHP7 poques vegades es necessita.

Funcions addicionals d’assistència

Molta gent prefereix utilitzar les versions abreviades _() en lloc de gettext(). Moltes biblioteques personalitzades de i18n també des de frameworks usen quelcom semblant a t(), per fer més breu el codi traduït. Tanmateix, aquesta és l’única funció que ofereix una drecera. Potser voldreu afegir al projecte alguns altres, com ara __() o _n() per a ngettext(), o potser una fantàstica _r() que uniria les cridades a gettext() i a sprintf(). Altres biblioteques, com ara Gettext d’oscarotero, també ofereixen funcions d’ajuda com aquestes.

En aquests casos, haureu de indicar a Gettext com extreure les cadenes d’aquestes funcions noves. No tingueu por; és molt fàcil. Es tracta només d’un camp del fitxer .po o d’una pantalla de configuració de Poedit. A l’editor, aquesta opció es troba dins de “Catàleg> Propietats> Paraules clau de font”. Recordeu-ho: Gettext ja coneix les funcions predeterminades de molts llenguatges, així que no tingueu por de si aquesta llista sembla buida. Heu d’incloure aquí les especificacions d’aquestes funcions noves, seguint un format específic:

  • si creeu quelcom com a t() que simplement retorna la traducció per a una cadena, podeu especificar-la com a t. Gettext sabrà que l’únic argument de funció és la cadena a traduir;
  • si la funció té més d’un argument, podeu especificar quin és la primera cadena i, si cal, també la forma plural. Per exemple, si anomenem la nostra funció així: __(‘un usuari’, ‘%d usuaris’, $number), l’especificació seria __: 1,2, és a dir, el primer número és el primer argument i el segon, el segon argument. Si el vostre número apareix com a primer argument, l’especificació seria __: 2,3, indicant que la primera forma és el segon argument, etc.

Després d’incloure aquestes noves regles al fitxer .po, una nova exploració extraurà les vostres cadenes tan fàcilment com abans.

Internacionalització en Twig

Per a facilitar l’ús de Gettext en Twig cal instal·lar les extensions de Twig. En Extensions de Twig trobareu les indicacions.

Activitat

Adapta el projecte de forma que la interfície web estiga internacionalitzada i localitzada en valencià, castellà i anglès.

Referències