mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
346 lines
14 KiB
Markdown
346 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 [http://framework.zend.com/manual/en/zend.date.html](Zend_Date API).
|
|
This means all formats are defined in
|
|
[http://framework.zend.com/manual/en/zend.date.constants.html#zend.date.constants.selfdefinedformats](ISO date format),
|
|
not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
|
|
|
|
### 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
|
|
|
|
## Adapting modules for i18n
|
|
|
|
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
|
|
|
|
Here is the function prototype of this translator function
|
|
|
|
:::php
|
|
function _t(string $entity [, string $string [, int $priority [, string $context]]]) {
|
|
|
|
|
|
**$entity:** The first parameter is the identifier, and is composed by a namespace and an entity name, with a dot separating them.
|
|
The main class name (i.e. the same one that the php name file) should usually be used as the namespace. This means that
|
|
if we are coding in the file LeftAndMain.php, the namespace should be 'LeftAndMain', and therefore the complete first
|
|
parameter would be 'LeftAndMain.ENTITY'. There is an exception to this rule. If you are using the same exactly string in two different files, for example in
|
|
A.php and B.php, and the string in B.php will always be the same string that in A.php, then you can 'declare' this
|
|
string in A.php with `_t('A.ENTITY','String that is used in A and B');`{php} and then in B.php simply write:
|
|
`_t('A.ENTITY');`{php} In this way if somewhere in the future you need to modify this string, you just need to edit it
|
|
in one file (A.php). Translators will also have to translate this string just once. Entity names are by convention written in uppercase. They have to be unique within their namespace, and its purpose is
|
|
to serve as an identificator to this string, together with the namespace. Having an unique identificator for each string
|
|
allows some features like change tracking. Therefore, a meaningful name is always welcomed, although not required. And
|
|
also, that's why you shouldn't change an existing entity name in the code, unless you have a good reason to do it.
|
|
|
|
**$string:** The second parameter is the string itself. It's not mandatory if you have set this same string in another place before
|
|
(using the same class and entity). So you could write `_t('ClassName.HELLO',"Hello")` and later `_t('ClassName.HELLO')`.
|
|
In fact, if you write the string in this second case, a warning will be issued when text-collecting to alert that you
|
|
are redeclaring an entity.
|
|
|
|
**$priority:** Priority parameter is an optional parameter and it can be used to set a translation priority. If a string is widely
|
|
used, it should have a high priority (PR_HIGH), in this way translators will be able to prioritise the translation of
|
|
this strings. If a string is extremely rarely shown, use PR_LOW. You can use PR_MEDIUM as well. Leaving this field blank
|
|
will be interpretated as a "normal" priority (some less than PR_MEDIUM). Using priorities allows translators to benefit from the 80/20 rule when translating, since typically there is a reduced
|
|
set of strings that are widely displayed, and a lot of more specific strings. Therefore, in a module with a considerable
|
|
amount of strings, where partial translations can be expected, priorities will help to have translated the most
|
|
displayed strings. If a string is in a class is inheritable, it's not recommended to establish a priority (we don't know about child
|
|
behavior a priori).
|
|
|
|
### Context
|
|
|
|
Last parameter is context, it's also optional. Sometimes short phrases or words can have several translations depending
|
|
upon where they are used, and Context serves as a way to tell translators more information about the string in these
|
|
cases where translating can be difficult, due to lack of context or ambiguity.
|
|
|
|
This context param can also be used with other situations where translation may need to know more than the original
|
|
string, for example with sprintf '%' params inside the string, since you can tell translators about the meaning of this
|
|
parameters.
|
|
|
|
:::php
|
|
//Example 4: Using context to hint information about a parameter
|
|
sprintf(
|
|
_t('CMSMain.RESTORED',
|
|
"Restored '%s' successfully",
|
|
PR_MEDIUM,
|
|
'Param %s is a title'
|
|
),
|
|
$title
|
|
)
|
|
|
|
|
|
### 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.
|
|
|
|
### sprintf()-support
|
|
|
|
Sprintf enables us to dynamically replace parts of a translated string, e.g. by a username or a page-title.
|
|
|
|
:::php
|
|
// in PHP-file
|
|
sprintf(
|
|
_t('CMSMain.RESTORED',"Restored '%s' successfully"),
|
|
$title
|
|
)
|
|
|
|
<div class="warning" markdown='1'>
|
|
**Caution**: In templates (*.ss)-files you can only use ONE argument for your sprintf-support, and can't use spaces
|
|
between parameters.
|
|
</div>
|
|
|
|
:::php
|
|
// in SS-template ($title must be available in the current template-scope)
|
|
<% sprintf(_t('CMSMain.RESTORED',"Restored '%s' successfully"),$title) %>
|
|
|
|
|
|
## 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 tables in PHP
|
|
|
|
Each module can have one language table per locale. These tables are just PHP files with array notations. By convention,
|
|
the files are stored in the /lang subfolder, and are named after their locale value, e.g. "en_US.php".
|
|
|
|
Example: sapphire/lang/en_US.php (extract)
|
|
|
|
:::php
|
|
// ...
|
|
$lang['en_US']['ImageUploader']['ATTACH'] = array(
|
|
'Attach %s',
|
|
PR_MEDIUM,
|
|
'Attach image/file'
|
|
);
|
|
$lang['en_US']['FileIFrameField']['NOTEADDFILES'] = 'You can add files once you have saved for the first time.';
|
|
// ...
|
|
|
|
|
|
Translation table: sapphire/lang/de_DE.php (extract)
|
|
|
|
:::php
|
|
$lang['de_DE']['ImageUploader']['ATTACH'] = '%s anhängen';
|
|
$lang['de_DE']['FileIframeField']['NOTEADDFILES'] = 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben';
|
|
|
|
|
|
## Javascript Usage
|
|
|
|
*Requires SilverStripe 2.3*
|
|
|
|
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(SAPPHIRE_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 sprintf()
|
|
|
|
:::js
|
|
// MYMODULE.MYENTITY contains "Really delete %s articles by %s authors?"
|
|
alert(ss.i18n.sprintf(
|
|
ss.i18n._t('MYMODULE.MYENTITY'),
|
|
42,
|
|
'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
|
|
|
|
* [http://www.i18nguy.com/](http://www.i18nguy.com/)
|
|
* [balbus.tk i18n notes](http://www.balbus.tk/internationalize)
|