Internationalization (i18n) and localization (l10n) issues are handled by the "language" class included by the "template.php" file and instantiated as the global variable $LANG in the backend.
The strategy of localization in TYPO3 is to translate all the parts of the TYPO3 Backend (TBE) interface which are available to everyday users of the CMS such as content editors, contributors, and to a certain extend, administrators. However all developer/admin-parts should remain in English.
The reason for keeping the adminstrator/developer parts in English is that those parts change and expand too quickly. Further it would be a huge task to both implement and translate. And the most important reason is that we want to keep a common vocabulary between developers in the international TYPO3 developer community. So in fact there are strong reasons for not translating the whole system into local languages!
Currently, local charsets are used by default inside TYPO3. However it is highly recommended to set $TYPO3_CONF_VARS['BE']['forceCharset'] = "utf-8" which will force the backend to run in utf-8 regardless of "native" charset. Forcing the charset to "utf-8" also means that all content in the database will be managed in "utf-8" and you might corrupt existing data if you set it after having added content in another charset.
Here is the basic facts about TYPO3s technology to handle localization of the backend interface:
Default language: The default language of TYPO3 is English. (Please respect this in extensions as well!)
Alternative languages: The list of available system languages is defined in the constant TYPO3_languages (hardcoded/defined in config_default.php). This "list" is two-char identification codes representing languages. The code usually reflects a ccTLD related to the language but can be any unique 2-char combination.
Label files: All labels for the backend (and frontend) are stored in “locallang-XML” (llXML) files located in extensions. They are named according to the scheme “locallang*.xml”. The default labels must be english! The system extension “lang” contains labels for the core system.
Notice: "locallang*.php" files is an old alternative still supported but deprecated; They contain the $LOCAL_LANG array in a PHP script which is simply included. Old “locallang.php” files can be converted to llXML files using the extension “extdeveval”
Localization methods: The "language" class (from sysext/lang/lang.php) contains methods for requesting labels out of llXML files in the backend (such as getLL() and sL()). The language class also contains an instance of the class "t3lib_cs" where the charsets used for each language are defined. The charset is detected by the "template" class and automatically set for the documents in the backend.
Translation of llXML files: On a local system this is handled by the extension “llxmltranslate”. Unless the llXML files contains inline translation or a specific reference to an external file, the “llxmltranslate” tool will write the translation to a corresponding filename in “typo3conf/l10n/[language key]/[extension key]” (recommended).
Language packs: Due to the large amount of languages for TYPO3 (more than 40) all llXML files in the core and extensions should contain only the default english labels. If no entry is found in the main file for translations, automatically a translation is looked for in “typo3conf/l10n/[language key]/[extension key]”. The directory “typo3conf/l10n/[language key]/” is called the “language pack”.
For more detailed information about Frontend localization in TYPO3, please refer to the document "Frontend Localization Guide" (doc_l10nguide).
Regardless of whether you want to help with an existing language or add a new language you must subscribe to the “translator” mailing list! That's rule number 1. Start out by announcing yourself and your intention on that list!
If you wish to add a new language to TYPO3, follow these steps:
First, a 2-char unique language key not yet used (Current Language key list: see t3lib/stddb/tbl_be.php:be_users:lang) and charset (utf-8 is default) must be agreed upon.
Contact a core developer and ask him to add the language key to the core. The steps for doing this is defined inside t3lib/config_default.php in a comment just above the definition of the constant “TYPO3_languages”.
As soon as the language is added to the core you can start translation using the extension “llxmltranslate” running on the new core. The language pack is automatically created in “typo3conf/l10n/[new language key]/”
Most likely you would like to translate TYPO3 with help from other native speakers from the community. These are the options:
Set up your own translation server:
Install TYPO3 and add those extensions you would like to support in you translation
Install “llxmltranslate” and use the backend module “llXML” for translation
Add user accounts for other translators (set their backend language to the language they are supposed to translate)
The result - the language pack in “typo3conf/l10n/[language key]/” is ready for distribution.
Pro: You have maximum freedom to keep the translation up-to-date with current core and extensions. You can distrubute the language pack as you like.
Con: You have to intiate the technical set up yourself.
Use official translation server:
Get URL, username and password from translator newsgroup.
If the language is just added you will have to wait until Kasper announces that the translation server is updated with the new core.
Pro: Easy solution, don't have to install TYPO3 yourself.
Con: You must accept long times between core updates to that server and cannot distrubute your work yourself.
This is still an open question at this point. The basic technical nature of a language pack is simply that it is a directory in typo3conf/l10n/ inside of which locallang-xml files are found in folders according to the position of their main file.
Scheme: “typo3conf/l10n/[lang]/[extkey]/[dir]+[filename]”
This leaves various possibilities of distribution and cooperation:
ZIP archives (download)? Language packs can be distributed as a zip file contain translations for any number of extensions. Installation of a language pack is no harder than unzipping a file in the right directory. Groups of extensions (like the core) can come bundled together or extensions can be packed alone. There is currently no public download place for such archives.
EM support? One could imagine that support was built into the Extension Manager:
Download: EM could traverse all installed extensions for locallang*.xml files and send a http-request to some repository for possible translations located there, then writing them into typo3conf/l10n/. EM could even do this transparently based on a configuration of which language packs are wanted for the installation.
Upload: EM could offer to upload local translations of extensions to a localization repository. This could be the solution for distributed translation of un-common extensions since typically one can expect the users of an extension to translate it if not already done - and thus, with a click of a button they might wish to share it.
CVS? Putting the whole language pack into CVS might be an idea for cooperation on a translation. But maybe there will be too many collisions, the language pack in CVS will by time contain translations for too many extensions most people don't use and it is not likely that all translators have CVS knowledge.
rsync? For both upload and download this could pose a lightweight alternative to CVS - especially for multiple translation servers. But it suffers many of the problems CVS has.
As a start I would make zip-archives, but my favourite is to integrate support in EM since:
It's going to be light weight http upload/download
EM knows which extensions are relevant on the system and can install language packs for extensions transparently as a parallel process to installing extensions.
Allows easy sharing of translations made all over the world (thus an answer to the missing “online translation” tool and translations are made when needed and with understanding of what is translated)
The locallang-XML files contain default labels, possibly inline translations and in addition a lot of meta data used in the translation tool (extension “llxmltranslate”).
Performance is great with the XML files. This is because the content of the XML file is cached based on the modification time on the xml file.
"locallang-XML" files store the translations of a single language in external files by default. The main reason for using external files for locallang-XML should be distribution considerations:
Typically installations need only English and 1-2 other backend languages, not all 40+!
It allows translators to work on translations independent of extension authors.
Splitting translations means we can better defend storing meta data for translators.
The format of locallang-XML files can look like this example (shows inline translation of danish):
<T3locallang>
<meta type="array">
<description>Standard Module labels for Extension Development Evaluator</description>
<type>module</type>
<csh_table/>
<labelContext type="array"/>
</meta>
<data type="array">
<languageKey index="default" type="array">
<label index="mlang_tabs_tab">ExtDevEval</label>
<label index="mlang_labels_tabdescr">The Extension Development Evaluator tool.</label>
</languageKey>
<languageKey index="dk" type="array">
<label index="mlang_tabs_tab">ExtDevEval</label>
<label index="mlang_labels_tabdescr">Evalueringsværktøj til udvikling af extensions.</label>
</languageKey>.... </data> <orig_hash type="array">
<languageKey index="dk" type="array">
<label index="mlang_tabs_tab" type="integer">114927868</label>
<label index="mlang_labels_tabdescr" type="integer">187879914</label>
</languageKey> </orig_hash></T3locallang>
You can refer to "TYPO3 Core API" for details about the XML format.
If you have locallang.php files in your extensions, please consider to convert them to llXML. This can be done with the extension “extdeveval” which contains a tool for that. After conversion there is another tool which will separate translations out into the language pack directories. This is of course also highly recommended so the remaining main llXML file contains only default labels!
A "locallang" file looks like this (sysext/lang/locallang_tca.php):
<?php$LOCAL_LANG = Array ( 'default' => Array ( 'pages' => 'Page', 'doktype.I.0' => 'Standard', 'doktype.I.1' => 'SysFolder', 'doktype.I.2' => 'Recycler', 'title' => 'Pagetitle:', 'php_tree_stop' => 'Stop page tree:', 'is_siteroot' => 'Is root of website:', 'storage_pid' => 'General Record Storage page:', 'be_users' => 'Backend user', 'be_groups' => 'Backend usergroup', 'sys_filemounts' => 'Filemount', ), 'dk' => Array ( 'pages' => 'Side', 'doktype.I.0' => 'Standard', 'doktype.I.1' => 'SysFolder', 'doktype.I.2' => 'Papirkurv', 'title' => 'Sidetitel:', 'php_tree_stop' => 'Stop sidetræ:', 'is_siteroot' => 'Siden er websitets rod:', 'storage_pid' => 'Generel elementlager-side:', 'be_users' => 'Opdaterings bruger', 'be_groups' => 'Opdaterings brugergruppe', 'sys_filemounts' => 'Filmount', ), 'de' => Array ( 'pages' => 'Seite', 'doktype.I.0' => 'Standard', 'doktype.I.1' => 'SysOrdner', 'doktype.I.2' => 'Papierkorb', 'title' => 'Seitentitel:', 'php_tree_stop' => 'Seitenbaum stoppen:', 'is_siteroot' => 'Ist Anfang der Webseite:', 'storage_pid' => 'Allgemeine Datensatzsammlung:', 'be_users' => 'Backend Benutzer', 'be_groups' => 'Backend Benutzergruppe', 'sys_filemounts' => 'Dateifreigaben', ),
....[lots of other languages defined]...
'sk' => Array ( 'pages' => 'Stránka', 'doktype.I.0' => 'Štandardná', 'doktype.I.1' => 'Systémová zložka', 'title' => 'Titulka stránky', ), 'lt' => Array ( 'pages' => 'Puslapis', 'doktype.I.0' => 'Standartinis', 'doktype.I.1' => 'Sisteminis Aplankas', 'doktype.I.2' => 'Ðiukðlinë', 'title' => 'Puslapio antraðtë:', 'php_tree_stop' => 'Stapdyti puslapio medá:', 'is_siteroot' => 'Ar svetainës ðakninis:', 'storage_pid' => 'Bendra Puslapio Áraðo saugykla:', 'be_users' => 'Administravimo pusës vartotojas', 'be_groups' => 'Administravimo pusës vartotojo grupë', 'sys_filemounts' => 'Bylø stendas', ),);?>
So the $LOCAL_LANG array has the syntax
$LOCAL_LANG[language_key][label_key] = 'label_value';
As you can see all available languages are located in the same file! However if a set of labels is very large it is inefficient to load all languages into memory when you need only the default plus the current language to be available (eg. Danish).
Therefore you can split locallang files into a structure with a main file (locallang*.php) and sub-files (locallang*.[langkey].php). An example is sysext/lang/locallang_core.php:
<?php/*** Core language labels. */$LOCAL_LANG = Array ( 'default' => Array ( "labels.openInNewWindow" => "Open in new window", "labels.goBack" => "Go back", "labels.makeShortcut" => "Create a shortcut to this page?", "labels.lockedRecord" => "The user '%s' began to edit this record %s ago.", "cm.open" => "Open",
... [lot of more labels here]....
"cm.save" => "Save", "cm.unzip" => "Unzip", "cm.info" => "Info", "cm.createnew" => "Create new", ), 'dk' => "EXT", 'de' => "EXT", 'no' => "EXT", 'it' => "EXT", 'fr' => "EXT", 'es' => "EXT", 'nl' => "EXT", 'cz' => "EXT", 'pl' => "EXT", 'si' => "EXT", 'fi' => "EXT", 'tr' => "EXT", 'se' => "EXT", 'pt' => "EXT", 'ru' => "EXT", 'ro' => "EXT", 'ch' => "EXT", 'sk' => "EXT", 'lt' => "EXT",);?>
The string token "EXT" set for all the other languages than "default" tells the "language" class that another file contains the language for this language key. For the Danish language this file would be "sysext/lang/locallang_core.dk.php":
<?php/*** Core language labels (dk)*/$LOCAL_LANG['dk'] = Array ( "labels.openInNewWindow" => "Åben i nyt vindue", "labels.goBack" => "Gå tilbage", "labels.makeShortcut" => "Opret genvej til denne side?", "cm.open" => "Åbn",... [lot of more labels here]....
"cm.save" => "Gem", "cm.unzip" => "Unzip", "cm.info" => "Info", "cm.createnew" => "Opret ny",);?>
A requirement is that this "sub-file" sets only it's own language key (here "dk") in the $LOCAL_LANG array. Thus simply including this file after the main file will add the whole "dk" key to the existing $LOCAL_LANG array with no need for array merging!
Notice another detail which is a general feature of $LOCAL_LANG arrays: The label key 'labels.lockedRecord' is not specified for the Danish translation. That simply means that the value of the "default" key (English) will be shown until that value will be added by the Danish translator!
Notice: locallang.php files are deprecated! Use locallang-XML files instead!
An old concept called “language-split” has been around for use with typically table-names, field names etc. in $TCA. This concept is based on a single string with labels separated by “|” according to the number of system languages defined in the TYPO3_languages constant. But this approach is now depricated for the future because it is not very scalable and it's VERY hard to maintain properly. Therefore the “locallang” concept is required for use anywhere a value is defined to be “language-splitted” (LS). Instead of specifying a number of labels separated with “|” you simply write a code, which refers to a locallang-file/label inside of that.
Syntax is “LLL:[file-reference of locallang file relative to PATH_site]:[key-name]:[extra settings]”.
File-reference should be a filename relative to PATH_site. You can prepend the reference with “EXT:[extkey]/” in order to refer to locallang-files from extensions.
For the extension “mininews” we have a field called “title”. Normally this would be translated into Danish like this in the $TCA:
"title" => Array (
"exclude" => 0,
"label" => "Title:|Titel:",
"config" => Array (
"type" => "input",
"size" => "30",
"eval" => "required",
)
),
But now we would create a file, “locallang_db.php” in the root of the extension directory. This would look like this:
<?php
$LOCAL_LANG = Array (
"default" => Array (
"tx_mininews_news.title" => "Title:",
),
"dk" => Array (
"tx_mininews_news.title" => "Titel:",
),
"de" => Array (
)
);
?>
As you can see there is an English (red) and Danish (green) translation. But the German is still missing.
Now, in the $TCA array we change the “language-splitted” label to this value instead:
"title" => Array (
"exclude" => 0,
"label" => "LLL:EXT:mininews/locallang_db.php:tx_mininews_news.title",
"config" => Array (
"type" => "input",
"size" => "30",
"eval" => "required",
)
),
As you can see it has now become a reference to the file “locallang_db.php” in the "mininews" extension. Inside this file we will pick the label “tx_mininews_news.title” (this associative key could be anything you decide. In this case I have just been systematic in my naming).
Notice how the reference to the locallang file is divided into three parts separated with a colon, marked with colors corresponding with the syntax mentioned before: “LLL:[file-reference of locallang file]:[key-name]:[extra settings]”.
The “extra-settings” are currently not used.
The previous section described the storage structure for translations: "locallang" files and $LOCAL_LANG arrays. But how are these values practically used?
Basically there are two approaches:
Call $LANG->getLL("label_key")
Call $LANG->sL("LLL:[file-reference of locallang file]:[key-name]")
These are described below.
This approach will simply return a label from the globally defined $LOCAL_LANG array. So prior to calling this function you must have included a locallang file (and possibly sub-file) in the global scope of the script.
There is a sister function, $LANG->getLLL("label_key", $LOCAL_LANG), which allows you to do the same thing, but pass along the $LOCAL_LANG array to use (instead of the global array).
Requires a locallang file to be manually included prior to use. See below.
This approach lets you get a label by a reference to the file where it exists and its label key: $LANG->sL("LLL:[file-reference of locallang file]:[key-name]"). That mode is initiated by a triple L (LLL:) prefix of the string.
The file-reference is a "locallang"-file in either PHP or XML format. It is not important to know in this case! Using the ".php" or ".xml" file ending will not matter as long as one of the files exist. TYPO3 will look for both file extensions and use the one it finds.
If not a "LLL:" string is prefixed then the input is exploded by a vertical bar (|) and each part is perceived as the label for the corresponding language in the TYPO3_languages constant. However this concept is depricated since it's impossible to maintain efficiently. Always use the "LLL:" references to proper locallang files. (See discussion of "language-splitted" syntax above).
$LANG->sL() requires no manual inclusion of a locallang file since that is done automatically. Typically used in table and field name labels in $TCA or in modules where a single value from the core locallang file is needed.
(See the example in the previous section '"language-splitted" syntax' in addition)
If you are using $LANG->getLL() for fetching labels in your modules (this is recommended) then you must make sure to include the locallang file with the labels during the initialization of your module. However you should not just include the file - rather use the API-function $LANG->includeLLFile() designed for that. There are three reasons for this:
If the locallang.php file is splitted into a main- and sub-file that is automatically handled by that function.
If any 'XLLFile' is configured to override the values in the default locallang file, that file will be included and the values merged onto the default array.
The file-reference is a "locallang"-file in either PHP or XML format. It is not important to know in this case! Using the ".php" or ".xml" file ending will not matter as long as the file exists. TYPO3 will look for both file extensions and use the one it finds.
Example from the "setup" module (red line includes locallang for that module):
require ($BACK_PATH.'init.php');
require ($BACK_PATH.'template.php');
$LANG->includeLLFile('EXT:setup/mod/locallang.php');
This function call will load the $LOCAL_LANG array from 'EXT:setup/mod/locallang.php' into the global memory space and thus make it available to $LANG->getLL(). If 'EXT:setup/mod/locallang.php' does not exist but 'EXT:setup/mod/locallang.xml' does, then the latter is parsed, loaded and everything is the same for TYPO3. Although you should probably use the correct file extension in the file reference (using ".xml" when the locallang file is actually a "locallang-XML" format file).
If you wish to not load the $LOCAL_LANG array into global space, but rather have it returned in a variable, just set the second optional argument true like this:
$myLocalLang = $LANG->includeLLFile('EXT:setup/mod/locallang.xml', 1);
TYPO3 offers an API for overriding LOCAL_LANG values in the backend by custom files you set up. Provided that the inclusion of the locallang file is handled by the language class then your custom file will be included after the real locallang file(s) and the arrays merged together. Lets look at an example:
We want to change the label of the logout button from "Logout" to "End session". What we do is this:
First, find out where the label is outputted so you can know the label key and locallang file. In this case the script "alt_menu.php" outputs the button which is generated by a function from the file "class.alt_menu_functions.inc". Looking into this file we find that the line "$LANG->sL('LLL:EXT:lang/locallang_core.php:buttons.logout')" fetches the label for the button.
Create an alternative $LOCAL_LANG array with the labelkeys you want to override. I have created the file "typo3conf/llor_test.php" which looks like this:
<?php
$LOCAL_LANG = array(
"default" => array(
"buttons.logout" => "End session",
),
"dk" => array(
"buttons.logout" => "Afslut admin",
)
);
?>
Notice how it contains both an English and Danish alternative.
Configure the script to override values in the file "EXT:lang/locallang_core.php"This is simply done by adding an entry in the $TYPO3_CONF_VARS['BE']['XLLfile'] array which points to the overriding file:
$TYPO3_CONF_VARS['BE']['XLLfile']['EXT:lang/locallang_core.php']='typo3conf/llor_test.php';
The filepath of "typo3conf/llor_test.php" is relative to the PATH_site constant. You could also keep the file in an extension in which case you would have to enter the file reference like 'EXT:myext/llor_test.php' - and the file will automatically be located wherever you extension is installed.
This example includes a function call to $LANG->sL(). If the labels are fetched by $LANG->getLL() as they are in most modules you will have to make sure that the locallang file you need to override was included by the function $LANG->includeLLFile() since that will detect any "XLLfile" you might have configured - otherwise the API will not work of course.