I don't believe myself to be an authority on this topic, nor this answer to be complete, but below I try to pull together a summary of several of the things you'll want to consider when adding localization to your plugins. Please correct me where I'm wrong and I'll try to make updates to this as I learn more until proper documentation exists.
Adding localization to your plugin will require addressing at least three different scenarios in the Control Panel:
- How are you saving your data in a localized way?
- How are you retrieving your data in a localized way?
- How are you managing the UI around switching between locales?
While there may be related settings, the primary thing that gets localized in Craft is the Element. As you localize your plugin you may need to ask: how does what I am trying to add localization support for relate to the Elements in the system?
Craft-managed tables that relate to localization
I believe Craft will manage the locale-specific behavior for several tables in the database for you:
- craft_elements_i18n
- craft_locales
- craft_routes
- craft_searchindex
- craft_templatecaches
Element-specific tables that relate to localization
Each respective Element Type will need to manage and make sure locale-related data is saved to the tables that relate to that specific Element Type.
In the case of Entries, Entries manage locale-specific data in the following tables:
- craft_content
- craft_entrydrafts
- craft_entryversions
- craft_sections_i18n
Categories manage locale-specific data with different requirements:
- craft_categorygroups_i18n
- craft_content
Localization and Field Types
Many fields get added directly to Elements via Field Layouts. By default, fields store the data they save as a field-specific column on the content table that relates to the Element the field has been added to. An Entry With a Plain Text field named stuff
will store it's data in the craft_content
table with the column name field_stuff
.
For fields that use this default method of storing their data, you do not need to add additional localization support. The craft_content
table already has a locale
column and populating that value will be handled by the Element that your field is assigned to.
For custom fields that are advanced enough to require a field-specific database table to store their data (consider an Address Field, perhaps), the plugin developer will need to add locale support (discussed in more detail below).
How are you saving your data in a localized way?
Elements
Craft does not do much to help you save your Element data in a locale-friendly way. You'll need to manage a lot of this on your own.
To start off, you'll want to give your Element an isLocalized
method and return true
. One class to read up on to understand a bit more about how localization works on Element Types is the IElementType Class.
While Craft appears to manage saving the locale data to craft_elements_i18n
for you, it appears Elements that support locales also have a secondary i18n
table that relates to something that I don't know what to call but tend to refer to as the Element Group. The Element Group for Entries is Sections, for Categories is Category Groups, for Craft Commerce Products is Product Types.
Entries
- craft_sections
- craft_sections_i18n
Categories
- craft_categorygroups
- craft_categorygroups_i18n
Commerce Products
- craft_commerce_producttypes
- craft_commerce_producttypes_i18n
It would seem that if your Element has URL support, and those URL Format settings are managed on the Element Group for that Element, you will need a similar i18n
table in your plugin for your Element.
Advanced Fields
Advanced fields will need to:
- Add a
locale
column to the database table that stores their field info
- Populate that locale column while saving data to their custom table in the Field Types
onAfterElementSave
method. The locale ID can be found via $this->element->locale
value in your Field Type class.
How are you retrieving your data in a localized way?
Elements
When Elements are retrieved from the database using an Element Query, the Element Types modifyElementsQuery()
method will be called. This gives the Element a chance to make changes to the query and is where you will need to update the query to ensure that the locale is being considered.
This is discussed in this question here and seems to make sense, however, it doesn't appear to be used in the Entry or Category Elements and I cannot explain why.
Advanced Fields
- Retrieve the locale-specific record from the database when retrieving the field data in the
getInputHtml
method. The locale ID can be found via $this->element->locale
value in your Field Type class.
How are you managing the UI around switching between locales?
When a user switches between locales while editing an Entry a few things need to happen. Craft appears to manage this with entries in part by reloading the page which probably also means they are re-querying the database for the content specific to the locale of the page being loaded.
http://happylager.dev/admin/entries/news/slug-1/en_us
http://happylager.dev/admin/entries/news/slug-1/es
The switch may actually be triggered by javascript. craft.js
has a class called Craft.ElementEditor
which includes a switchLocale
method and calls elements/getEditorHtml and an updateForm method. I don't know much more than that!
The input field values for the locale
will need to be updated to the locale ID of the locale you want to save your data for:
<input type="hidden" name="locale" value="en_us">
You will want to give visual indications of which locale is in use as well. This may include the controls the user uses to switch between languages as well as pass the locale value to any form macros that should display a locale indicator.
{{ forms.textField({
label: "Title"|t,
locale: element.locale,
name: 'title',
value: element.title,
errors: element.getErrors('title'),
}) }}
The locale
attribute in the above macro will output a locale specific indicator in the label
tag of the form: <span class="locale">{{ locale }}</span>
.