Skip to content

Gettext'like Wrapper class to set locale environment and then either use *nix gettext(), or JSON files for translations. Also handles plurals!

License

Notifications You must be signed in to change notification settings

ravenlost/PHP_Locale

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CorbeauPerdu\i18n\Locale Class

Wrapper class to set locale environment and then either use *nix gettext(), or JSON files for translations!

See: LocaleUsageExamples.php

You have two choices for locale messages: Gettext MO files or JSON files.

If using JSON files, here are two examples of proper formats:

Example JSON French file: /fr_FR/prestadesk.json
{
  "": {
    "domain": "prestadesk",
    "language": "fr_FR",
    "nplurals": "1",
    "plural": "(n > 1)"
  },

  "Welcome, %s!": "Bienvenu, %s!",
  "This page will show the dashboard": "Cette page affichera le tableau de bord",

  "I wrote a line of code": "J'ai écris une ligne de code",
  "I wrote %d lines of code": "J'ai un écris %d lignes de code"
}

Example JSON Serbian file: /sr_CS/prestadesk.json
{
  "": {
    "domain": "prestadesk",
    "language": "sr_CS",
    "nplurals": "3",
    "plural": "((n%10==1 && n%100!=11) ? 0 : ((n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20)) ? 1 : 2))"
  },

  "Welcome, %s!": "Dobrodosli, %s!",
  "This page will show the dashboard": "Ova stranica ce prikazati kontrolnu tablu",

  "I wrote a line of code": "Napisao sam liniju koda",
  "I wrote %d lines of code": [
    "Napisao sam %d liniju koda",
    "Napisao sam %d linije koda",
    "Napisao sam %d linija koda"
  ]
}

Note that the "plural" value needs to be a properly formated ternary condition for PHP!
You'll find gettext valid plurals here: http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
However, since these are for gettext (meaning C-Style ternary conditions), you need to adjust them to be valid in PHP!

ELSE, if you are too lazy to adjust them :p, set the constructor's $usePluralFormsFromGettext to TRUE,
and replace the "nplurals" and "plural" with a copy/pasted "plural-forms" from the link above, like so:

"plural-forms": "nplurals=2; plural=(n > 1);"
"plural-forms": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"

The script will attempt to regex parse the nplurals and plural value, and rebuild proper ternary conditions for PHP!

Notes about $locales array:

Your supported $locales should be in the form of [ 'desired lang passed from querystring, cookie, etc' => 'real installed lang mapping on server' ]
$locale = [ 'en' => 'en_US', 'fr' => 'fr_FR' ];

In this case, 'en_US' and 'fr_FR' folders are expected to exist in your specified locales directory!

Even if you use 'phrases' as your language keys (say the actual english message to translate, if english is your default),
you still need to add this default locale to the $locales array passed to Locale() constructor!

AND

you need to create either one, based on your config, a gettext translation file (domain.mo),
OR a JSON translation file, both with at least the headers in it:

$locales_dir/<default_lang>/LC_MESSAGES/$domain.mo  OR
$locales_dir/<default_lang>/$domain.json

If you don't do this, you'll get exceptions when it checks for the locale's translation files, or exceptions when it tries to validate plural-forms!

No, in reality because you're using phrases as keys, you won't need real translation files.
However, doing it this way, you can always decide to use keywords as keys (i.e. profile.title) later on,
and then create complete translation files ALSO for your default language holding these keyword keys

Functions:

  Locale::gettext()   or Locale::_()    // Lookup a message in the current domain, singular form
  Locale::ngettext()  or Locale::_n()   // Lookup a message in the current domain, plurial form
  Locale::dgettext()  or Locale::_d()   // Lookup a message in a given domain, singular form
  Locale::dngettext() or Locale::_dn()  // Lookup a message in a given domain, plurial form

  Locale::loadDomain()                     // load additionnal domain (translation files), on top of the default one

  Locale::getDefaultDomain()               // get the default domain set
  Locale::getMissingTranslations()         // get a list of missing translations in the page (call this at the very end of your script!)
  Locale::getMissingTranslations_toWeb()   // ... printed for a webpage!
  Locale::getLoadedDomains()               // get list of loaded domains: if using JSON, will also include the actual domain messages data
  Locale::getLoadedDomains_toWeb()         // ... printed for a webpage!
  Locale::getLang() or Locale::getLocale() // returns the currently set lang/locale
  Locale::getLang4HtmlTag()                // ... replaces '_' with '-' for proper format to put in <html> and <meta> html tags
  Locale::getTranslationsAsJSON()          // get a JSON string holding all translations for a given $domain
                                           // useful to populate say a javascript variable and thus get translations even in JS!
                                           // can be used only if using JSON files
  Locale::setFormatMessages4Web()          // if set to true, all returned messages will be htmlentities()'d and line breaks '\n' replaced with '<br/>'
  Locale::setFormatMessages4WebInclPlaceholders() // if formatMessages4Web is on, do we want to also format the placeholder values? Default is true!
  Locale::switchLang()                     // switch running locale to another language; all previously loaded domains will be re-loaded in desired language!

Notes about the *ngetttext() functions for plurals:
If you are using actual keywords as keys for message translations (i.e. profile.title):

  • if using gettext MOs : both singular and pural values needs to be the same:
    Locale::ngettext('cat.amount', 'cat.amount', 3)
  • if using JSON files : both singular and pural values needs to be different:
    Locale::ngettext('cat.amount', 'cat.amount.plural', 3)

Notes about sprintf functionnality:
You can provide any of the *gettext() functions 1 or many 'v' optional argument(s).
These values will be used to replace the sprintf's placeholders! i.e.:
Locale::_n("Hello %s, you have %d mail.", "Welcome, %s! You have %d emails.", "John", 5);
Locale::_n("%.1f hour/week", "%.1f hours/week", 37.5000, 37.5000);

Extra tip:
If you plan on using gettext, and use tools such as POedit, or xgettext, etc. to get and manage your translations,
you can add Sources Keywords to enable it to find all needed translations from all source code.

For this script, use the following keywords:

gettext:1
ngettext:1,2
dgettext:2
dngettext:2,3
_:1
_n:1,2
_d:2
_dn:2,3

If Using POedit, just add this to the header of your PO:

"X-Poedit-KeywordsList: gettext:1;ngettext:1,2;dgettext:2;dngettext:2,3;_:1;_n:1,2;_d:2;_dn:2,3\n"

When using JSON files, personnally I'd also use this method to retrieve all my needed translations into a PO file, and then just create my JSON file based on my values in my PO!

Known Bugs

There is currently a small issue when using plural tests (any of the *ngettext() functions). Passing a float value as the testing number to know if we need the plural or singular form will not work properly: it currently can only receive INTeger values! Need to also investigate if the same problem exists with JS_Locale.

Example: ngettext('I have one cat', 'I have many cats', 1.6)

Will output: I have one cat, even though, there is more than one (1.6). Okay, using cats as an example is ridiculous (no one can have 1.6 cats! lol), but it still shows the problem.

About

Gettext'like Wrapper class to set locale environment and then either use *nix gettext(), or JSON files for translations. Also handles plurals!

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages