silverstripe-framework/docs/en/topics/i18n.md
2012-06-28 14:49:55 +02:00

361 lines
14 KiB
Markdown

# i18n
## Introduction
The i18n class (short for "internationalization") in SilverStripe enables you to display templates and PHP code in
different languages based on your global settings and the preferences of your website users. This process is also known
as l10n (short for "localization").
For translating any content managed through the CMS or stored in the database,
please use the "[translatable](http://github.com/silverstripe/silverstripe-translatable)" module.
This page aims to describe the low-level functionality of the i18n-API. It targets developers who:
* are involved in creating templates in different languages
* want to build their own modules with i18n capabilities
* want to make their PHP-code (e.g. form labels) i18n-ready
Please note that this project scope currently **doesn't include full support for format conversion in dates or
currencies**. Check our [roadmap](http://open.silverstripe.com/roadmap).
## Usage
### Enabling i18n
The i18n class is enabled by default.
### Setting the locale
To set the locale you just need to call `[api:i18n::set_locale()]` passing, as a parameter, the name of the locale that you
want to set.
:::php
//Example 1: setting the locale
i18n::set_locale('de_DE'); //Setting the locale to German (Germany)
i18n::set_locale('ca_AD'); //Setting to Catalan (Andorra)
Once we set a locale, all the calls to the translator function will return strings according to the set locale value, if
these translations are available. See
[unicode.org](http://unicode.org/cldr/data/diff/supplemental/languages_and_territories.html) for a complete listing of
available locales.
### Getting the locale
As you set the locale you can also get the current value, just by calling `[api:i18n::get_locale()]`.
### Declaring the content language in HTML {#declaring_the_content_language_in_html}
To let browsers know which language they're displaying a document in, you can declare a language in your template.
:::html
//'Page.ss' (HTML)
<html lang="$ContentLocale">
//'Page.ss' (XHTML)
<html lang="$ContentLocale" xml:lang="$ContentLocale" xmlns="http://www.w3.org/1999/xhtml">
Setting the '<html>' attribute is the most commonly used technique. There are other ways to specify content languages
(meta tags, HTTP headers), explained in this [w3.org article](http://www.w3.org/International/tutorials/language-decl/).
### Date and time formats
Formats can be set globally in the i18n class. These settings are currently only picked up by the CMS, you'll need
to write your own logic for any frontend output.
:::php
i18n::set_date_format('dd.MM.YYYY');
i18n::set_time_format('HH:mm');
Most localization routines in SilverStripe use the [Zend_Date API](http://framework.zend.com/manual/en/zend.date.html).
This means all formats are defined in
[ISO date format](http://framework.zend.com/manual/en/zend.date.constants.html#zend.date.constants.selfdefinedformats),
not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
### i18n in URLs
By default, URLs for pages in SilverStripe (the `SiteTree->URLSegment` property)
are automatically reduced to the allowed allowed subset of ASCII characters.
If characters outside this subsetare added, they are either removed or (if possible) "transliterated".
This describes the process of converting from one character set to another
while keeping characters recognizeable. For example, vowels with french accents
are replaced with their base characters, `pâté` becomes `pate`.
In order to allow for so called "multibyte" characters outside of the ASCII subset,
limit the character filtering in the underlying class: `URLSegmentFilter::$default_use_transliterator = false`
Please refer to [W3C: Introduction to IDN and IRI](http://www.w3.org/International/articles/idn-and-iri/) for more details.
### i18n in Form Fields
Date- and time related form fields support i18n ([api:DateField], [api:TimeField], [api:DatetimeField]).
:::php
i18n::set_locale('ca_AD');
$field = new DateField(); // will automatically set date format defaults for 'ca_AD'
$field->setLocale('de_DE'); // will not update the date formats
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // sets typical 'de_DE' date format, shows as "23. Juni 1982"
Defaults can be applied globally for all field instances through [api:DateField::set_default_config()]
and [api:TimeField::set_default_config()]. If no 'locale' default is set on the field, [api:i18n::get_locale()]
will be used.
Important: Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases,
fields created through [api:DataObject::getCMSFields()] will get their i18n settings from a specific member
The [api:DateField] API can be enhanced by JavaScript, and comes with
[jQuery UI datepicker](http://jqueryui.com/demos/datepicker/) capabilities built-in.
The field tries to translate the date formats and locales into a format compatible with jQuery UI
(see [api:DateField_View_JQuery::$locale_map_] and [api:DateField_View_JQuery::convert_iso_to_jquery_format()]).
:::php
$field = new DateField();
$field->setLocale('de_AT'); // set Austrian/German locale
$field->setConfig('showcalendar', true);
$field->setConfig('jslocale', 'de'); // jQuery UI only has a generic German localization
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // will be transformed to 'dd. MM yy' for jQuery
## Translating text
Adapting a module to make it localizable is easy with SilverStripe. You just need to avoid hardcoding strings that are
language-dependent and use a translator function call instead.
:::php
// without i18n
echo "This is a string";
// with i18n
echo _t("Namespace.Entity","This is a string");
All strings passed through the `_t()` function will be collected in a separate language table (see "Collecting entities"
below), which is the starting point for translations.
### The _t() function
The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional.
* **$entity:** Unique identifier, composed by a namespace and an entity name, with a dot separating them. Both are arbitrary names, although by convention we use the name of the containing class or template. Use this identifier to reference the same translation elsewhere in your code.
* **$string:** (optional) The original language string to be translated. Only needs to be declared once, and gets picked up the [text collector](#collecting-text).
* **$string:** (optional) Natural language comment (particularly short phrases and individual words)
are very context dependent. This parameter allows the developer to convey this information
to the translator.
* **$array::** (optional) An array of injecting variables into the second parameter
:::php
//Example 4: Using context to hint information about a parameter
_t('CMSMain.RESTORED',
"Restored {value} successfully",
'This is a message when restoring a broken part of the CMS',
array('value' => $itemRestored)
);
### Usage
There're two types of files in a module where you can use this _t() function: code files (under code folder) and
template files (under templates)
* In code files, in order to ask for a translated string, we have to write a normal php call to this function.
Example:
:::php
_t('LeftAndMain.HELLO','Site content',PR_HIGH,'Menu title');
_t('LeftAndMain.FILESIMAGES','Files & Images',PR_HIGH);
_t('LeftAndMain.NEWSLETTERS','Newsletters');
* In template files these calls are written slightly different to ease readibility, diminish overhead and allow a
cleaner template file. Calls can be placed anywhere, but they are preceded and followed by `<% and %>` as usual in the
SilverStripe templating language, and the first parameter is omitted (namespace in template files is always the file
itself).
Therefore, the following would be a valid use in templates:
:::ss
<a href="http://www.silverstripe.com" title="<% _t('VISIT','Visit www.silverstripe.com') %>">
Using SS templating variables in the translatable string (e.g. $Author, $Date..) is not currently supported.
### Injection Support
Variable injection in `_t()` allows us to dynamically replace parts of a translated string, e.g. by a username or a page-title. The named parameters also allow flexible ordering of placeholders,
which might vary depending on the used language.
:::php
// in PHP-file
_t(
'CMSMain.RESTORED',
"Restored {title} successfully"),
array('title' => $title)
);
:::php
// in SS-template ($Name must be available in the current template-scope)
<%t MYPROJECT.INJECTIONS "Hello {name} {greeting}" name="$Name" greeting="good to see you" %>
Note that you can still use `sprintf()` wrapped around a `_t()` call
for your substitutions. In contrast to `sprintf()`, our API has a more translator friendly
placeholder syntax, as well as more graceful fallback if not all placeholders are found
(an outdated translation with less placeholders will throw a notice rather than a fatal error).
## Collecting text
To collect all the text in code and template files we have just to visit:
`http://<mysite>/dev/tasks/i18nTextCollectorTask`
Text collector will then read the files, build the master string table for each module where it finds calls to the
underscore function, and tell you about the created files and any possible entity redeclaration.
If you want to run the text collector for just one module you can use the 'module' parameter:
`http://<mysite>/dev/tasks/i18nTextCollectorTask/?module=cms`
<div class="notice" markdown='1'>
**Note**: You'll need to install PHPUnit to run the text collector (see [testing-guide](/topics/testing)).
</div>
## Language definitions (3.x)
Each module can have one language table per locale, stored by convention in the `lang/` subfolder.
The translation is powered by `[Zend_Translate](http://framework.zend.com/manual/en/zend.translate.html)`,
which supports different translation adapters, dealing with different storage formats.
By default, SilverStripe 3.x uses a YAML format (through the [Zend_Translate_RailsYAML adapter](https://github.com/chillu/zend_translate_railsyaml)).
Example: framework/lang/en.yml (extract)
en:
ImageUploader:
Attach: 'Attach %s'
UploadField:
NOTEADDFILES: 'You can add files once you have saved for the first time.'
Translation table: framework/lang/de.yml (extract)
de:
ImageUploader:
ATTACH: '%s anhängen'
UploadField:
NOTEADDFILES: 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben'
Note that translations are cached across requests.
The cache can be cleared through the `?flush=1` query parameter,
or explicitly through `Zend_Translate::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL)`.
## Language definitions (2.x)
In SilverStripe 2.x, the tables are just PHP files with array notations,
stored based on their locale name (e.g. "en_US.php").
Example: framework/lang/en_US.php (extract)
:::php
// ...
$lang['en_US']['ImageUploader']['ATTACH'] = array(
'Attach %s',
'Attach image/file'
);
$lang['en_US']['UploadField']['NOTEADDFILES'] = 'You can add files once you have saved for the first time.';
// ...
Translation table: framework/lang/de_DE.php (extract)
:::php
$lang['de_DE']['ImageUploader']['ATTACH'] = '%s anhängen';
$lang['de_DE']['UploadField']['NOTEADDFILES'] = 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben';
In order to enable usage of PHP language definitions in 3.x, you need to register a legacy adapter
in your `mysite/_config.php`:
:::php
i18n::register_translator(
new Zend_Translate(array(
'adapter' => 'i18nSSLegacyAdapter',
'locale' => i18n::default_locale(),
'disableNotices' => true,
)),
'legacy',
9 // priority lower than standard translator
);
## Javascript Usage
i18n in javascript works with mostly the same assumption as its PHP-equivalent.
### Requirements
Add the i18n library requirement to your code.
:::php
Requirements::javascript(FRAMEWORK_DIR . "/javascript/i18n.js");
Each language has its own language table in a separate file. To save bandwidth, only two tables are actually loaded by
the browser: The current locale, and the default locale as a fallback. The Requirements class has a special method to
determine these includes: Just point it to a directory instead of a file, and the class will figure out the includes.
:::php
Requirements::add_i18n_javascript('<my-module-dir>/javascript/lang');
### Translation Tables in JavaScript
Translation tables are automatically included as required, depending on the configured locale in *i18n::get_locale()*.
As a fallback for partially translated tables we always include the master table (en_US.js) as well.
Master Table (mymodule/javascript/lang/en_US.js)
:::js
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('en_US', {
'MYMODULE.MYENTITY' : "Really delete these articles?"
});
}
Example Translation Table (mymodule/javascript/lang/de_DE.js)
:::js
ss.i18n.addDictionary('de_DE', {
'MYMODULE.MYENTITY' : "Artikel wirklich löschen?"
});
### Basic Usage
:::js
alert(ss.i18n._t('MYMODULE.MYENTITY'));
### Advanced Usage with injection
:::js
// MYMODULE.MYENTITY contains "Really delete {answer} articles by {author} authors?"
alert(ss.i18n._t('MYMODULE.MYENTITY'),
array('answer' => 42, 'author' => 'Douglas Adams')
));
// Displays: "Really delete 42 articles by Douglas Adams?"
## Limitations
* No detecting/conversion of character encodings (we rely fully on UTF-8)
* Translation of graphics/assets
* Usage of gettext (too clumsy, too many requirements)
* Displaying multiple languages/encodings on the same page
## Links
* [Help to translate](/misc/translation-process) - Instructions for online collaboration to translate core, as well as your own modules
* [http://www.i18nguy.com/](http://www.i18nguy.com/)
* [balbus.tk i18n notes](http://www.balbus.tk/internationalize)