Joomla 4.x Tutorial - Extension Development - Using Language Files

Modify this post

Your goal was to make your extension multilingual! That's why you didn't enter the texts directly into the program code. Specifically, I mean the texts that are displayed in the browser. You had prepared everything so that you use special files. These are uncomplicated exchangeable. So far you have seen cryptic texts because of that. In this part we translate the unattractive language strings.

Joomla language files are used

Even if your target audience speaks English and you only support this language it is important to use a language file for texts you display in the front-end or back-end of the component. This way it is possible for users to overwrite texts without editing the source code. Under some circumstances a user prefers to write first name instead of name in the column header

For impatient people

View the changed program code in the Diff Viewgithub.com/astridx/boilerplate/compare/t7...t8 and incorporate these changes into your development version.

Step by step

In the following overview, the newly added files are marked with a background and the changed ones are outlined.

Overview of the files edited in this chapter

The frontend view and the administration area each use their own language files. Unlike the frontend, where there is only one, the backend needs two - *.sys.ini and *.ini. Briefly explained: The file with the extension sys.ini is used to translate the XML installation file as well as the menu elements. The ini is responsible for the rest. This has the advantage that during the installation and for the construction of the menu only the loading of small text files is necessary. A disadvantage is that some language strings have to be entered twice. You can find out more in the article International Enhancements which has a section on the file *.sys.ini.

The addition of the English language files is mandatory. All other languages are optional. The reason for this is that if a file is missing, the English version is used by default. If a Frenchman installs the extension - which contains German and English language files - on his Joomla with the default language French, the texts will be displayed in English.

Side Note: Special features

Would you like to see exactly how the ini file is parsed? At php.net you will find the description of the function that does this work.

Commenting out

You can mark a line as a comment using a semicolon ;.

; Joomla! Project
; (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
; License GNU General Public License version 2 or later; see LICENSE.txt
Note : All ini files need to be saved as UTF-8
....

Escaping

There are characters that have a special meaning - for example, the prefix characters. This meaning can be removed with a backslash .

...
COM_CONTACT_CONTACT_REQUIRED="<strong class=\"red\">*</strong> Required field"

...

Variables

Sometimes the output of the language string depends on a variable. The function Text::sprintf ensures that you do not have to compose the text in a complicated way in the programme code. Instead of the variable in the language file, enter a character with the prefix %. For example, you can use %s.

...
COM_CONTACT_CHECKED_OUT_BY="Checked out by %s"
...

In the PHP code, the call then looks like this.

...
Text::sprintf('COM_CONTACT_CHECKED_OUT_BY', $checkoutUser->name)
...

The value of $checkoutUser->name is inserted instead of the first variable in the language string. Here in the example instead of %s.

Unfortunately, you cannot determine which variable $checkoutUser->name belongs to which language string %s. The values are assigned in order.

singular/singular

There is singular or singular and plural or plural and the Joomla language strings support this.

Let us take the call

...
$message = Text::plural('COM_FOOS_N_ITEMS_FEATURED', count($ids));
...

as an example.

Depending on whether count($ids) has the value 1 or 2 the language string COM_FOOS_N_ITEMS_FEATURED_1 or COM_FOOS_N_ITEMS_FEATURED_2 is used. If count($ids) has neither 1 nor 2, COM_FOOS_N_ITEMS_FEATURED is used as the fallback position.

...
COM_FOOS_N_ITEMS_FEATURED="%d foos featured."
COM_FOOS_N_ITEMS_FEATURED_1="Foo featured."
COM_FOOS_N_ITEMS_FEATURED_2="Two foos featured."
...

New files

Create the following six files to support the German language in addition to English.

The left side of the equal sign in the language string, for example COM_FOOS_CONFIGURATION" in COM_FOOS_CONFIGURATION="Foo Options", is always in upper case. Normally the extension name is at the beginning, in our case it is COM_FOOS. After that you ideally add a short description. Here you describe briefly what this string is used for. Make sure that you do not use spaces. Only letters and underscores are allowed.

The right side of the language string, for example Foo Options" in COM_FOOS_CONFIGURATION = "Foo Options", is the actual text that will be displayed on the site. When your extension is translated into another language, the translator only changes this right side of the language string in his language file. The right side is enclosed in quotation marks.

administrator/components/comfoos/ language/de-DE/comfoos.ini

We add the German language version for the administration area with the files 'administrator/components/comfoos/ language/en-DE/comfoos.ini' and 'administrator/components/comfoos/ language/en-DE/comfoos.sys.ini'.

Don't be confused if you see a lot of texts. These are not all used at the moment. I'm already inserting the text for the future chapters here.

administrator/components/comfoos/ language/de-DE/comfoos.ini

<!--  https://raw.githubusercontent.com/astridx/boilerplate/t8/src/administrator/components/com_foos/language/de-DE/com_foos.ini -->

COM_FOOS="[PROJECT_NAME]"
COM_FOOS_CONFIGURATION="Foo Optionen"

COM_FOOS_MANAGER_FOO_NEW="Neu"
COM_FOOS_MANAGER_FOO_EDIT="Bearbeiten"
COM_FOOS_MANAGER_FOOS="Foo Manager"

COM_FOOS_TABLE_TABLEHEAD_NAME="Name"
COM_FOOS_TABLE_TABLEHEAD_ID="ID"
COM_FOOS_ERROR_FOO_NOT_FOUND="Foo nicht gefunden"

COM_FOOS_FIELD_NAME_LABEL="Name"

COM_FOOS_FIELD_FOO_SHOW_CATEGORY_LABEL="Namensschild anzeigen"
COM_FOOS_FIELD_CONFIG_INDIVIDUAL_FOO_DESC="Diese Einstellungen gelten für alle foo."
COM_FOOS_FIELD_CONFIG_INDIVIDUAL_FOO_DISPLAY="Foo"
Naming conventions

Each language file is marked with an abbreviation, which is defined in ISO-639 and ISO-3166: The first two lower case letters name the language. For German this is de and en for English.

After the hyphen, the two capital letters indicate the country. For example, Swiss German can be distinguished from DE by CH or Austrian by AT. A frolder named de-CH contains the translation for Switzerland and de-AT the Austrian variant.

administrator/components/comfoos/ language/de-DE/comfoos.sys.ini

As mentioned before, you need two language files: one ending with .ini and one ending with sys.ini. The sys.ini is primarily used during installation and for displaying the menu items and the sys.ini for everything else.

The reason for this dichotomy is performance. When installing or setting up the navigation in the backend, it is thus not necessary to load all language strings. In small extensions this advantage of two language files is not big in my opinion. Therefore I partially fill both files with the same language strings. This way I am on the safe side. This is not professional, but it has proven to be suitable for me in practice.

administrator/components/comfoos/ language/de-DE/comfoos.sys.ini

<!--  https://raw.githubusercontent.com/astridx/boilerplate/t8/src/administrator/components/com_foos/language/de-DE/com_foos.sys.ini -->

COM_FOOS="[PROJECT_NAME]"
COM_FOOS_XML_DESCRIPTION="Foo Komponente"

COM_FOOS_INSTALLERSCRIPT_PREFLIGHT="<p>Alles hier passiert vor der Installation / Aktualisierung / Deinstallation der Komponente</p>"
COM_FOOS_INSTALLERSCRIPT_UPDATE="<p>TDie Komponente wurde aktualisiert</p>"
COM_FOOS_INSTALLERSCRIPT_UNINSTALL="<p>Die Komponente wurde deinstalliert</p>"
COM_FOOS_INSTALLERSCRIPT_INSTALL="<p>Die Komponente wurde installiert</p>"
COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT="<p>Alles hier passiert nach der Installation / Aktualisierung / Deinstallation der Komponente</p>"

COM_FOOS_FOO_VIEW_DEFAULT_TITLE="Ein einzelnes Foo"
COM_FOOS_FOO_VIEW_DEFAULT_DESC="Dies ist ein Link zu den Informationen für ein Foo."
COM_FOOS_SELECT_FOO_LABEL="Wählen Sie ein foo aus"

COM_FOOS_CHANGE_FOO="Ändern Sie ein foo"
COM_FOOS_SELECT_A_FOO="Wählen Sie ein foo aus"

administrator/components/comfoos/ language/en-GB/comfoos.ini

I had already written it: The English versions of the language files are mandatory.

administrator/components/comfoos/ language/en-GB/comfoos.ini

<!--  https://raw.githubusercontent.com/astridx/boilerplate/t8/src/administrator/components/com_foos/language/en-GB/com_foos.ini -->

COM_FOOS="[PROJECT_NAME]"
COM_FOOS_CONFIGURATION="Foo Options"
COM_FOOS_FOOS="Foos"
COM_FOOS_CATEGORIES="Categories"

COM_FOOS_MANAGER_FOO_NEW="New"
COM_FOOS_MANAGER_FOO_EDIT="Edit"
COM_FOOS_MANAGER_FOOS="Foo Manager"

COM_FOOS_TABLE_TABLEHEAD_NAME="Name"
COM_FOOS_TABLE_TABLEHEAD_ID="ID"
COM_FOOS_ERROR_FOO_NOT_FOUND="Foo not found"

COM_FOOS_FIELD_NAME_LABEL="Name"

COM_FOOS_FIELD_FOO_SHOW_CATEGORY_LABEL="Show name label"
COM_FOOS_FIELD_CONFIG_INDIVIDUAL_FOO_DESC="These settings apply for all foo."
COM_FOOS_FIELD_CONFIG_INDIVIDUAL_FOO_DISPLAY="Foo"

COM_FOOS_FIELD_PUBLISH_DOWN_LABEL="Finish Publishing"
COM_FOOS_FIELD_PUBLISH_UP_LABEL="Start Publishing"
COM_FOOS_N_ITEMS_PUBLISHED="%d foos published."
COM_FOOS_N_ITEMS_PUBLISHED_1="%d foo published."
COM_FOOS_N_ITEMS_UNPUBLISHED="%d foos unpublished."
COM_FOOS_N_ITEMS_UNPUBLISHED_1="%d foo unpublished."
COM_FOOS_N_ITEMS_CHECKED_IN_1="%d foo checked in."
COM_FOOS_N_ITEMS_CHECKED_IN_MORE="%d foos checked in."
COM_FOOS_N_ITEMS_FEATURED="%d foos featured."
COM_FOOS_N_ITEMS_FEATURED_1="Foo featured."
COM_FOOS_N_ITEMS_UNFEATURED="%d foos unfeatured."
COM_FOOS_N_ITEMS_UNFEATURED_1="Foo unfeatured."

COM_FOOS_EDIT_FOO="Edit Foo"
COM_FOOS_NEW_FOO="New Foo"

COM_FOOS_HEADING_ASSOCIATION="Association"
COM_FOOS_CHANGE_FOO="Change a foo"
COM_FOOS_SELECT_A_FOO="Select a foo"

COM_FOOS_TABLE_CAPTION="Foo Table Caption"

COM_FOOS_N_ITEMS_ARCHIVED="%d foos archived."
COM_FOOS_N_ITEMS_ARCHIVED_1="%d foo archived."
COM_FOOS_N_ITEMS_DELETED="%d foos deleted."
COM_FOOS_N_ITEMS_DELETED_1="%d foo deleted."
COM_FOOS_N_ITEMS_TRASHED="%d foos trashed."
COM_FOOS_N_ITEMS_TRASHED_1="%d foo trashed."
COM_FOO_MANAGER_FOOS="Foos"

COM_FOOS_FIELD_PARAMS_NAME_LABEL="Show Name"

COM_FOOS_FILTER_SEARCH_DESC="Search in foo name."
COM_FOOS_FILTER_SEARCH_LABEL="Search Foos"

administrator/components/comfoos/ language/en-GB/comfoos.sys.ini

Also the file is administrator/components/com_foos/ language/en-GB/com_foos.sys.ini mandatory.

administrator/components/comfoos/ language/en-GB/comfoos.sys.ini

<!--  https://raw.githubusercontent.com/astridx/boilerplate/t8/src/administrator/components/com_foos/language/en-GB/com_foos.sys.ini -->

COM_FOOS="[PROJECT_NAME]"
COM_FOOS_XML_DESCRIPTION="Foo Component"

COM_FOOS_INSTALLERSCRIPT_PREFLIGHT="<p>Anything here happens before the installation/update/uninstallation of the component</p>"
COM_FOOS_INSTALLERSCRIPT_UPDATE="<p>The component has been updated</p>"
COM_FOOS_INSTALLERSCRIPT_UNINSTALL="<p>The component has been uninstalled</p>"
COM_FOOS_INSTALLERSCRIPT_INSTALL="<p>The component has been installed</p>"
COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT="<p>Anything here happens after the installation/update/uninstallation of the component</p>"

COM_FOOS_FOO_VIEW_DEFAULT_TITLE="Single Foo"
COM_FOOS_FOO_VIEW_DEFAULT_DESC="This links to the information for one foo."
COM_FOOS_SELECT_FOO_LABEL="Select a foo"

COM_FOOS_CHANGE_FOO="Change a foo"
COM_FOOS_SELECT_A_FOO="Select a foo"

COM_FOOS_FOO_VIEW_WITHHEAD_TITLE="Single Foo with a headertext"
COM_FOOS_FOO_VIEW_WITHHEAD_DESC="This links to the information for one foo with a headertext."

COM_FOOS_FEATURED_VIEW_DEFAULT_TITLE="Featured Foos"
COM_FOOS_FEATURED_VIEW_DEFAULT_DESC="This view lists the featured foos."

COM_FOOS_FORM_VIEW_DEFAULT_DESC="Create a new foo."
COM_FOOS_FORM_VIEW_DEFAULT_TITLE="Create Foo"

COM_FOOS_CATEGORY_VIEW_DEFAULT_TITLE="Category"

components/comfoos/ language/de-DE/comfoos.ini

In the frontend there is only the .ini - so no sys.ini. The file components/com_foos/language/en-DE/com_foos.ini implements the German language.

components/comfoos/ language/de-DE/comfoos.ini

<!--  https://raw.githubusercontent.com/astridx/boilerplate/t8/src/components/com_foos/language/de-DE/com_foos.ini -->

COM_FOOS_NAME="Name: "

components/comfoos/ language/en-GB/comfoos.ini

We add the mandatory English version in the file components/com_foos/ language/en-GB/com_foos.ini.

components/comfoos/language/en-GB/comfoos.ini

<!--  https://raw.githubusercontent.com/astridx/boilerplate/t8/src/components/com_foos/language/en-GB/com_foos.ini -->

COM_FOOS_NAME="Name: "
COM_FOOS_FOO_NAME_LABEL="Name"
COM_FOOS_FIELD_NAME_LABEL="Name"
COM_FOOS_NEW_FOO="New"
COM_FOOS_EDIT_FOO="Edit"
JGLOBAL_FIELDSET_DISPLAY_OPTIONS="Display"
JGLOBAL_FIELDSET_PUBLISHING="Publishing"
COM_FOOS_FIELD_PARAMS_NAME_LABEL="Label"
JFIELD_ALT_LAYOUT_LABEL="Layout"
COM_FOOS_FIELD_PUBLISH_UP_LABEL="Publishing start"
COM_FOOS_FIELD_PUBLISH_DOWN_LABEL="Publishing end"

In the next chapters more language strings will be added. I will not mention them separately there. I have already integrated most of them into the sample files in this lesson. This way I avoid that the files appear in the diff view and blow it up unnecessarily. Specifically, I mean the diff view of the program code of the various chapters on Github, which I refer to here.

Modified files

administrator/components/com_foos/ foos.xml

So that the language files are copied during an installation, we add the <folder>language</folder> entry for the frontend and the backend.

administrator/components/com_foos/ foos.xml

 	</uninstall>
 	<!-- Frond-end files -->
 	<files folder="components/com_foos">
		<folder>language</folder>
 		<folder>src</folder>
 		<folder>tmpl</folder>
 	</files>

 		<files folder="administrator/components/com_foos">
 			<filename>foos.xml</filename>
 			<folder>forms</folder>
			<folder>language</folder>
 			<folder>services</folder>
 			<folder>sql</folder>
 			<folder>src</folder>
Where are the language files ideally stored?

Joomla's own components store the files for the administration area in the folder /administrator/language/en-GB/ and those for the site in the folder /language/en-GB/. This is the first place Joomla looks for the language files. For this reason, it was common for extension developers to put their files here. Sometimes it is more straightforward to put them in your own component folder. In our example, this is the folder administrator/components/com_foos/language/en-GB/ and components/com_foos/language/en-GB/ for the frontend. This is the place where Joomla looks for the language file if it doesn't find anything suitable in the directory /administrator/language/en-GB / and / language/en-GB respectively.

To place your files together with Joomla's own language files, you add the <language> tag to the installation file. Here is an example from com_contact where you need to adjust the value of the folder parameter to your structure:

...
	<files folder="components/com_foos">
...
	</files>

	<languages folder="site">
		<language tag="en-GB">language/en-GB.com_foos.ini</language>
	</languages>

	<administration>
...
		<languages folder="admin">
			<language tag="en-GB">language/en-GB.com_contact.ini</language>
			<language tag="en-GB">language/en-GB.com_contact.sys.ini</language>
		</languages>
	</administration>
...

components/com_foos/ tmpl/foo/default.php

So far we have output the name without a label in the frontend echo $this->item->name;. Now we add a label. For this we consider the different languages. The following code causes that instead of Text::_('COM_FOOS_NAME') in the frontend the string is output, which is entered in the corresponding language file. If there is a Spanish language file with the entry COM_FOOS_FIELD_NAME_LABEL="Nombre" and the Spanish language is active in the frontend, then Nombre is output. If the German language is active and there is the German language file with the entry COM_FOOS_FIELD_NAME_LABEL="Name", the word Name is displayed.

components/com_foos/ tmpl/foo/default.php

 \defined('_JEXEC') or die;
?>

<?php
echo $this->item->name;
use Joomla\CMS\Language\Text;

echo Text::_('COM_FOOS_NAME') . $this->item->name;

Test your Joomla component

  1. install your component in Joomla version 4 to test it:

Copy the files in the administrator folder into the administrator folder of your Joomla 4 installation.
Copy the files in the components folder into the components folder of your Joomla 4 installation.

A new installation is not necessary. Continue using the files from the previous part.

If you do a new installation, you will notice that the hints in the installation script are now translated.

Joomla language files are used

  1. open the view of your component in the administration area and frontend and make sure that the texts are readable and not cryptic anymore.

Joomla language files are used

  1. sample the innovation. Create language files for different languages and change the default language in Joomla. Make sure that Joomla translates correctly.
Modify this post

Comments