2011-02-07 07:48:44 +01:00
|
|
|
# Translation
|
|
|
|
|
|
|
|
## Introduction
|
|
|
|
|
|
|
|
This page introduces developers to using the CMS for creating content in multiple languages.
|
|
|
|
|
|
|
|
Please see [i18n](/topics/i18n) for a internationalization, globalization and localization support of built-in datatypes as well
|
|
|
|
as translating templates and PHP code.
|
|
|
|
|
|
|
|
Translations can be enabled for all subclasses of `[api:DataObject]`, so it can easily be implemented into existing code
|
|
|
|
with minimal interference.
|
|
|
|
|
|
|
|
Warning: If you're upgrading from a SilverStripe version prior to 2.3.2, please migrate your datamodel before using the
|
|
|
|
extension (see below).
|
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
*SilverStripe 2.3.2*
|
|
|
|
|
|
|
|
## Screenshots
|
|
|
|
|
|
|
|
![](_images/translatable4_small.png)
|
|
|
|
|
|
|
|
*Translated website*
|
|
|
|
|
|
|
|
|
|
|
|
![](_images/translatable1.png)
|
|
|
|
|
|
|
|
*CMS: Language dropdown*
|
|
|
|
|
|
|
|
![](_images/translatable2.png)
|
|
|
|
|
|
|
|
*CMS: Translatable field with original value*
|
|
|
|
|
|
|
|
![](_images/translatable3.png)
|
|
|
|
|
|
|
|
*CMS: Create a new translation*
|
|
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
### Configuration
|
|
|
|
|
|
|
|
#### ThroughObject::add_extension()
|
|
|
|
|
|
|
|
Enabling Translatable through *Object::add_extension()* in your *mysite/_config.php*:
|
|
|
|
|
|
|
|
:::php
|
|
|
|
Object::add_extension('SiteTree', 'Translatable');
|
|
|
|
Object::add_extension('SiteConfig', 'Translatable'); // 2.4 or newer only
|
|
|
|
|
|
|
|
|
|
|
|
#### Through $extensions
|
|
|
|
|
|
|
|
:::php
|
|
|
|
class Page extends SiteTree {
|
|
|
|
static $extensions = array(
|
|
|
|
"Translatable"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
Make sure to rebuild the database through /dev/build after enabling `[api:Translatable]`.
|
2011-02-07 07:48:44 +01:00
|
|
|
Use the correct set_default_locale() before building the database
|
|
|
|
for the first time, as this locale will be written on all new records.
|
|
|
|
|
|
|
|
#### Setting the default locale
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
<div class="notice" markdown='1'>
|
|
|
|
**Important:** If the "default language" of your site is not english (en_US), please ensure to set the appropriate default
|
|
|
|
language for your content before building the database with Translatable enabled
|
|
|
|
</div>
|
|
|
|
|
|
|
|
Example:
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
:::php
|
|
|
|
Translatable::set_default_locale(<locale>);
|
|
|
|
// Important: Call add_extension() after setting the default locale
|
|
|
|
Object::add_extension('SiteTree', 'Translatable');
|
|
|
|
|
|
|
|
|
|
|
|
For the Translatable class, a "locale" consists of a language code plus a region code separated by an underscore,
|
|
|
|
for example "de_AT" for German language ("de") in the region Austria ("AT").
|
|
|
|
See http://www.w3.org/International/articles/language-tags/ for a detailed description.
|
|
|
|
|
|
|
|
To ensure that your template declares the correct content language, please see [i18n](i18n#declaring_the_content_language_in_html).
|
|
|
|
|
|
|
|
### Usage
|
|
|
|
|
|
|
|
Getting a translation for an existing instance:
|
|
|
|
|
|
|
|
:::php
|
|
|
|
$translatedObj = Translatable::get_one_by_locale('MyObject', 'de_DE');
|
|
|
|
|
|
|
|
|
|
|
|
Getting a translation for an existing instance:
|
|
|
|
|
|
|
|
:::php
|
|
|
|
$obj = DataObject::get_by_id('MyObject', 99); // original language
|
|
|
|
$translatedObj = $obj->getTranslation('de_DE');
|
|
|
|
|
|
|
|
|
|
|
|
Getting translations through Translatable::set_reading_locale().
|
2011-03-08 22:05:51 +01:00
|
|
|
This is *not* a recommended approach, but sometimes unavoidable (e.g. for `[api:Versioned]` methods).
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
:::php
|
|
|
|
$origLocale = Translatable::get_reading_locale();
|
|
|
|
Translatable::set_reading_locale('de_DE');
|
|
|
|
$obj = Versioned::get_one_by_stage('MyObject', "ID = 99");
|
|
|
|
Translatable::set_reading_locale($origLocale);
|
|
|
|
|
|
|
|
|
|
|
|
Creating a translation:
|
|
|
|
|
|
|
|
:::php
|
|
|
|
$obj = new MyObject();
|
|
|
|
$translatedObj = $obj->createTranslation('de_DE');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Usage for SiteTree
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
`[api:Translatable]` can be used for subclasses of SiteTree as well.
|
2011-02-07 07:48:44 +01:00
|
|
|
If a child page translation is requested without the parent
|
|
|
|
page already having a translation in this language, the extension
|
|
|
|
will recursively create translations up the tree.
|
|
|
|
Caution: The "URLSegment" property is enforced to be unique across
|
|
|
|
languages by auto-appending the language code at the end.
|
|
|
|
You'll need to ensure that the appropriate "reading language" is set
|
|
|
|
before showing links to other pages on a website through $_GET['locale'].
|
|
|
|
Pages in different languages can have different publication states
|
2011-03-08 22:05:51 +01:00
|
|
|
through the `[api:Versioned]` extension.
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
Note: You can't get Children() for a parent page in a different language
|
|
|
|
through set_reading_locale(). Get the translated parent first.
|
|
|
|
|
|
|
|
:::php
|
|
|
|
// wrong
|
|
|
|
Translatable::set_reading_lang('de_DE');
|
|
|
|
$englishParent->Children();
|
|
|
|
// right
|
|
|
|
$germanParent = $englishParent->getTranslation('de_DE');
|
|
|
|
$germanParent->Children();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Translating custom properties
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
Keep in mind that the `[api:Translatable]` extension currently doesn't support the exclusion of properties from being
|
|
|
|
translated - all custom properties will automatically be fetched from their translated record on the database. This means
|
|
|
|
you don't have to explicitly mark any custom properties as being translatable.
|
2011-02-07 07:48:44 +01:00
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
The `[api:Translatable]` decorator applies only to the getCMSFields() method on DataObject or SiteTree, not to any fields
|
|
|
|
added in overloaded getCMSFields() implementations. See Translatable->updateCMSFields() for details. By default, custom
|
|
|
|
fields in the CMS won't show an original readonly value on a translated record, although they will save correctly. You can
|
2011-02-07 07:48:44 +01:00
|
|
|
attach this behaviour to custom fields by using Translatable_Transformation as shown below.
|
|
|
|
|
|
|
|
:::php
|
|
|
|
class Page extends SiteTree {
|
|
|
|
|
|
|
|
public static $db = array(
|
|
|
|
'AdditionalProperty' => 'Text',
|
|
|
|
);
|
|
|
|
|
|
|
|
function getCMSFields() {
|
|
|
|
$fields = parent::getCMSFields();
|
|
|
|
|
|
|
|
// Add fields as usual
|
|
|
|
$additionalField = new TextField('AdditionalProperty');
|
|
|
|
$fields->addFieldToTab('Root.Content.Main', $additionalField);
|
|
|
|
|
|
|
|
// If a translation exists, exchange them with
|
|
|
|
// original/translation field pairs
|
|
|
|
$translation = $this->getTranslation(Translatable::default_locale());
|
|
|
|
if($translation && $this->Locale != Translatable::default_locale()) {
|
|
|
|
$transformation = new Translatable_Transformation($translation);
|
|
|
|
$fields->replaceField(
|
|
|
|
'AdditionalProperty',
|
|
|
|
$transformation->transformFormField($additionalField)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Translating theHomepage
|
|
|
|
|
|
|
|
Every homepage has a distinct URL, the default language is /home, a German translation by default would be /home-de_DE.
|
|
|
|
They can be accessed like any other translated page. If you want to access different homepages from the "root" without a
|
|
|
|
URL, add a "locale" GET parameter. The German homepage would also be accessible through /?locale=de_DE.
|
|
|
|
|
|
|
|
For this to work, please ensure that the translated homepage is a direct translation of the default homepage, and not a
|
|
|
|
new page created through "Create page...".
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
### Translation groups
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
Each translation can have an associated "master" object in another language which it is based on,
|
|
|
|
as defined by the "MasterTranslationID" property. This relation is optional, meaning you can
|
|
|
|
create translations which have no representation in the "default language".
|
|
|
|
This "original" doesn't have to be in a default language, meaning
|
|
|
|
a french translation can have a german original, without either of them having a representation
|
|
|
|
in the default english language tree.
|
|
|
|
Caution: There is no versioning for translation groups,
|
|
|
|
meaning associating an object with a group will affect both stage and live records.
|
|
|
|
|
|
|
|
SiteTree database table (abbreviated)
|
|
|
|
| ID | URLSegment | Title | Locale |
|
|
|
|
| -- | ---------- | ----- | ------ |
|
|
|
|
| 1 | about-us | About us | en_US |
|
|
|
|
| 2 | ueber-uns | Über uns | de_DE |
|
|
|
|
| 3 | contact | Contact | en_US |
|
|
|
|
|
|
|
|
SiteTree_translationgroups database table
|
|
|
|
| TranslationGroupID | OriginalID |
|
|
|
|
| ------------------ | ---------- |
|
|
|
|
| 99 | 1 |
|
|
|
|
| 99 | 2 |
|
|
|
|
| 199 | 3 |
|
|
|
|
|
|
|
|
|
|
|
|
### CharacterSets
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
<div class="warning" markdown='1'>
|
|
|
|
**Caution:** Does not apply any character-set conversion, it is assumed that all content
|
2011-02-07 07:48:44 +01:00
|
|
|
is stored and represented in UTF-8 (Unicode). Please make sure your database and
|
|
|
|
HTML-templates adjust to this.
|
2011-03-08 22:05:51 +01:00
|
|
|
</div>
|
2011-02-07 07:48:44 +01:00
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
### "Default" languages
|
2011-02-07 07:48:44 +01:00
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
<div class="warning" markdown='1'>
|
|
|
|
**Important:** If the "default language" of your site is not english (en_US),
|
2011-02-07 07:48:44 +01:00
|
|
|
please ensure to set the appropriate default language for
|
2011-03-08 22:05:51 +01:00
|
|
|
your content before building the database with Translatable enabled
|
|
|
|
</div>
|
|
|
|
|
|
|
|
Example:
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
:::php
|
|
|
|
Translatable::set_default_locale(<locale>);
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
### Locales and language tags
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
For the Translatable class, a "locale" consists of a language code plus a region code separated by an underscore,
|
|
|
|
for example "de_AT" for German language ("de") in the region Austria ("AT").
|
2011-03-08 22:05:51 +01:00
|
|
|
See [http://www.w3.org/International/articles/language-tags/](http://www.w3.org/International/articles/language-tags/)
|
|
|
|
for a detailed description.
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
Uninstalling/Disabling
|
|
|
|
|
|
|
|
Disabling Translatable after creating translations will lead to all
|
|
|
|
pages being shown in the default sitetree regardless of their language.
|
|
|
|
It is advised to start with a new database after uninstalling Translatable,
|
|
|
|
or manually filter out translated objects through their "Locale" property
|
|
|
|
in the database.
|
|
|
|
|
|
|
|
## Recipes
|
|
|
|
|
|
|
|
|
|
|
|
### Switching languages
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
A widget now exists to switch between languages, and is [available here](http://www.silverstripe.org/Language-Chooser-Widget/).
|
|
|
|
You can easily make your own switchers with the following basic tools. To stay friendly to caches and search engines, each
|
|
|
|
translation of a page must have a unique URL.
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
By URL:
|
|
|
|
|
|
|
|
:::php
|
|
|
|
http://<mysite>/mypage/?locale=de_DE
|
|
|
|
|
|
|
|
|
|
|
|
By user preference (place this in your Page_Controller->init() method):
|
|
|
|
|
|
|
|
:::php
|
|
|
|
$member = Member::currentUser();
|
|
|
|
if($member && $member->Locale) {
|
|
|
|
Translatable::set_reading_locale($member->Locale);
|
|
|
|
}
|
|
|
|
|
|
|
|
### Templates
|
|
|
|
|
|
|
|
As every page has its own unique URL, language selection mostly happens explicitly: A user requests a page, which always
|
|
|
|
has only one language. But how does a user coming to your English default language know that there's a Japanese version
|
|
|
|
of this page?
|
|
|
|
By default, SilverStripe core doesn't provide any switching of languages through sessions or browser cookies. As a
|
|
|
|
SEO-friendly CMS, it contains all this information in the URL. Each page in SilverStripe is aware of its translations
|
|
|
|
through the *getTranslations()* method. We can use this method in our template to build a simple language switcher. It
|
|
|
|
shows all available translations in an unordered list with links to the same page in a different language. The example
|
2011-03-08 22:05:51 +01:00
|
|
|
below can be inserted in any of your templates, for example `themes/blackcandy/templates/Layout/Page.ss`.
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
:::php
|
|
|
|
<% if Translations %>
|
|
|
|
<ul class="translations">
|
|
|
|
<% control Translations %>
|
|
|
|
<li class="$Locale.RFC1766">
|
|
|
|
<a href="$Link" hreflang="$Locale.RFC1766"
|
|
|
|
title="$Title">
|
|
|
|
<% sprintf(_t('SHOWINPAGE','Show page in %s'),$Locale.Nice) %>
|
|
|
|
</a>
|
|
|
|
</li>
|
|
|
|
<% end_control %>
|
|
|
|
</ul>
|
|
|
|
<% end_if %>
|
|
|
|
|
|
|
|
|
|
|
|
Keep in mind that this will only show you available translations for the current page. The $Locale.Nice casting will
|
|
|
|
just work if your locale value is registered in i18n::get_common_locales().
|
|
|
|
|
|
|
|
### Page-control
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
If you want to put static links in your template, which link to a site by their url, normally you can use the `<% control
|
|
|
|
Page(page-url) %>`. For sites which use Translatable, this is not possible for more than one language, because the url's
|
2011-02-07 07:48:44 +01:00
|
|
|
of different pages differ.
|
2011-03-08 22:05:51 +01:00
|
|
|
|
2011-02-07 07:48:44 +01:00
|
|
|
For this case place the following function in your Page_Controller:
|
|
|
|
|
|
|
|
:::php
|
|
|
|
public function PageByLang($url, $lang) {
|
|
|
|
$SQL_url = Convert::raw2sql($url);
|
|
|
|
$SQL_lang = Convert::raw2sql($lang);
|
|
|
|
|
|
|
|
$page = Translatable::get_one_by_lang('SiteTree', $SQL_lang, "URLSegment = '$SQL_url'");
|
|
|
|
|
|
|
|
if ($page->Locale != Translatable::get_current_locale()) {
|
|
|
|
$page = $page->getTranslation(Translatable::get_current_locale());
|
|
|
|
}
|
|
|
|
return $page;
|
|
|
|
}
|
|
|
|
|
|
|
|
So, for example if you have a german page "Kontakt", which should be translated to english as "Contact", you may use:
|
|
|
|
|
|
|
|
<% control PageByLang(Kontakt,de_DE) %>
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
The control displays the link in the right language, depending on the current locale.
|
|
|
|
|
2011-02-07 07:48:44 +01:00
|
|
|
Example:
|
|
|
|
|
|
|
|
<% control PageByLang(Kontakt,de_DE) %>
|
|
|
|
<h2><a href="$Link" title="$Title">$Title</a></h2>
|
|
|
|
<% end_control %>
|
|
|
|
|
|
|
|
|
|
|
|
### Language Chooser Widget
|
|
|
|
|
|
|
|
You can use a widget on your website to provide a list of links for switching languages:
|
|
|
|
[download](http://silverstripe.org/Language-Chooser-Widget-2/)
|
|
|
|
|
|
|
|
|
|
|
|
### Enabling the _t() function in templates
|
|
|
|
|
|
|
|
If you're looking to use [the _t() function](http://doc.silverstripe.com/doku.php?id=i18n#the_t_function) in template
|
|
|
|
files, you'll need to [set the i18n locale](/topics/translation#setting_the_i18n_locale) first.
|
|
|
|
|
|
|
|
(The reasoning is as follows: Translatable doesn't set the i18n locale. Historically these were two separate systems,
|
|
|
|
but they're reasonably interchangeable for a front-end website. The distinction is mainly valid for the CMS, because you
|
2011-03-08 22:05:51 +01:00
|
|
|
want the CMS to be in English (`[api:i18n]`), but edit pages in different languages (`[api:Translatable]`).)
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
### Migrating from 2.1 datamodel
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
The datamodel of `[api:Translatable]` changed significantly between its original release in SilverStripe 2.1 and SilverStripe
|
2011-02-07 07:48:44 +01:00
|
|
|
2.3.2. See our [discussion on the
|
|
|
|
mailinglist](http://groups.google.com/group/silverstripe-dev/browse_thread/thread/91e26e1f78d3c1b4/bd276dd5bbc56283?lnk=gst&q=translatable#bd276dd5bbc56283).
|
|
|
|
|
|
|
|
To migrate a database that was built with SilverStripe 2.1.x or 2.2.x, follow these steps:
|
|
|
|
|
|
|
|
* Upgrade your SilverStripe installation to at least 2.3.2 (see [upgrading](/installation/upgrading))
|
|
|
|
* Backup your database content
|
|
|
|
* Login as an administrator
|
2011-03-08 22:05:51 +01:00
|
|
|
* Run `http://mysite.com/dev/build`
|
|
|
|
* Run `http://mysite.com/dev/tasks/MigrateTranslatableTask`
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
Please see the `[api:MigrateTranslatableTask]` for
|
|
|
|
limitations of this migration task - not all your data will be preserved.
|
|
|
|
|
|
|
|
|
|
|
|
### Setting the i18n locale
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
You can set the `[api:i18n]` locale value which is used to format dates, currencies and other regionally different values to
|
|
|
|
the same as your current page locale.
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
:::php
|
|
|
|
class Page_Controller extends ContentController {
|
|
|
|
public function init() {
|
|
|
|
parent::init();
|
|
|
|
|
|
|
|
if($this->dataRecord->hasExtension('Translatable')) {
|
|
|
|
i18n::set_locale($this->dataRecord->Locale);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
### Adding a new locale
|
|
|
|
|
2011-03-08 22:05:51 +01:00
|
|
|
The `[api:i18n]` logic has lookup tables for common locales in i18n::$common_locales, which is a subset of i18n::$all_locales.
|
|
|
|
If your locale is not present here, you can simply add it through `mysite/_config.php`:
|
2011-02-07 07:48:44 +01:00
|
|
|
|
|
|
|
:::php
|
|
|
|
i18n::$common_locales['de_AT'] = 'Deutsch (Oestereich)';
|
|
|
|
|
|
|
|
This should e.g. enable you to use `$Locale.Nice` in template code.
|
|
|
|
|
|
|
|
|
|
|
|
## Related
|
|
|
|
|
|
|
|
* [translate.silverstripe.org](http://translate.silverstripe.org): Starting point for community-driven translation of the Silverstripe UI
|
|
|
|
* [i18n](i18n): Developer-level documentation of Silverstripe's i18n capabilities
|
|
|
|
* `[api:Translatable]`: DataObject-interface powering the website-content translations
|
|
|
|
* ["Translatable ModelAdmin" module](http://silverstripe.org/translatablemodeladmin-module/): An extension which allows
|
2011-03-08 22:05:51 +01:00
|
|
|
translations of `[api:DataObject]`s inside `[api:ModelAdmin]`
|