Building Multilingual Sites with October CMS v2 and RainLab Translate Plugin for Localization and RTL Support
As businesses expand globally, websites must support multiple languages and locales. One of our client’s systems was built on October CMS v2 and required support for both Left-to-Right (LTR) and Right-to-Left (RTL) languages. This article explores how we utilized the RainLab Translate plugin to address multilingual compatibility and RTL challenges for our client’s site.
Configuring RainLab Translate in the Backend
RainLab Translate is essential for managing multilingual content in October CMS, enabling translations for CMS pages, static content, and dynamic elements.
Installing and Setting Up RainLab Translate
We first installed RainLab Translate via the command line:
php artisan plugin:install Rainlab.Translate
After installation, we configured the available languages by going to Settings > Translate Settings, adding a primary language and secondary languages for each locale:
RainLab Translate uses these configurations for localization purposes in both the frontend and backend.
Model Translation
We needed to translate dynamic content managed by our models, which RainLab.Translate supports seamlessly. Models can have their attributes translated by using the Rainlab\Translate\Behaviors\TranslatableModel behavior, specifying which attributes to translate in the class.
class Question extends Model
{
public $implement = [
\Rainlab\Translate\Behaviors\TranslatableModel
];
# this array holds the fields you want translated
pubic $translatable = ['label', 'question'];
}
Rainlab Translate automatically adjusts the input form fields in the backend editor, providing a dropdown for managing content in different languages:
Model's fields.yaml:
fields:
label:
label: Label
# Rainlab Translate will handle inputs for localization
type: text
question:
label: Question
type: textarea
Editor
The default language content is stored in its original database table, while translated content is stored in the rainlab_translate_attributes table as JSON strings. This table acts as an extension to the original model's table, allowing October CMS to manage and retrieve translated attributes while leaving the original model and database untouched.
Configuring Rainlab Translate in the Frontend
Since our version of October CMS does not have the multisite capability of newer versions, we implemented a custom solution to handle country-specific subsites from a single codebase.
Microsite Setup with .env.xx Files
We implemented a microsite structure using .env.xx files for each region. This setup allowed us to maintain one codebase while managing different country configurations (e.g., API keys, database connections, locales). For example:
.env.au → APP_LOCALE=en-au
.env.mx → APP_LOCALE=es-mx
We implemented logic to select the relevant .env.xx configuration based on the base URL of the microsites. For instance, /au loads the .env.au configuration.
The Dilemma of Multiple Languages
A particular challenge arose when handling multi-language microsites, such as Saudi Arabia, where both English and Arabic are required. Splitting the configuration into .env.sa-en and .env.sa-ar did not make sense, as it would treat Saudi Arabia as two separate regions, complicating non-app operations. This approach would also require duplicating every page and configuration, defeating the purpose of using RainLab Translate.
The .env.sa Setup:
To solve this, we used a single .env.sa file for Saudi Arabia. The locale was managed through the URL structure, where the format /xx-yy represented xx as the country code and yy as the language code. This method allowed us to determine the correct configuration file via the country code and the appropriate locale via the language code, simplifying the management of multiple languages.
Handling Backend and Frontend Locale
As we used a single codebase for multiple microsites, we encountered discrepancies between the frontend app locale and the backend locale. In the backend, we wanted to simplify the locale to just the language code (e.g., en, ar) for ease of use by content managers. In the frontend, however, we needed to handle country-specific locales like ar-sa (Arabic for Saudi Arabia) to utilize October CMS’ language system for the frontend (we’ll touch on this later):
Frontend Locale:
/plugin
├── /lang
│ ├── /ar-sa
│ ├── /en-sa
│ ├── /en-au
│ └── /en
To bridge this gap, we developed a custom LocaleSetter component by extending RainLab Translate’s LocalePicker. This component ensured that while RainLab Translate used the backend locale (en, ar), the frontend could handle the correct country-specific locale.
How the LocaleSetter Works
The LocaleSetter component initializes a TranslatorHandler that interacts with RainLab Translate’s Translator instance to manage the active locale. Here's the simplified code:
public function init(): void {
$this->translator = new TranslatorHandler();
$this->translator->setLocale();
}
public function setLocale(): bool {
# for example, 'ar-sa' → getAppLang() → 'ar'
# (map frontend locale to backend locale for Rainlab Translate to use)
$this->translator->setLocale(LangHelper::getAppLang(), false);
# We reset the app locale here so that it properly reads our
# frontend lang configuration,
# 'ar' → 'ar-sa' (map backend locale to frontend locale)
\App::setLocale(locale: $_SERVER['APP_LOCALE']);
return true;
}
By isolating localization logic within the LocaleSetter, we minimized changes to the broader codebase and kept the system modular, allowing for easy scaling as new regions and languages were added. If any microsites in the future needed language switching, their pages just needed this component.
Using October CMS Language Files for Static Content
As mentioned above, one of the main reasons for our need to set the frontend app locale was to capitalize on October CMS’s language system. Each language in the frontend had its own lang.php file for storing UI text, button labels, and other static content.
For example:
/plugin/sa-en/lang.php for English
/plugin/sa-ar/lang.php for Arabic
This allowed us to reference translation keys dynamically, loading the appropriate content for each locale:
sa-ar/lang.php
<?php
return [
'theme' => [
'welcome_message' => 'مرحبًا بك في هذه التدوينة' ,
'contact_us' => 'اتصل بنا ',
]
];
sa-en/lang.php
<?php
return [
'theme' => [
'welcome_message' => 'welcome to this blog post' ,
'contact_us' => 'contact us',
]
];
We can access these properties utilizing the Lang facade via Lang::get(‘<plugin_directory>::lang.<prop>’). We can set this as a sitewide variable to use for our static html content:
Event::listen(
'cms.page.beforeRenderPage',
function (\Cms\Classes\Controller $controller, \Cms\Classes\Page $page)
{
$controller->vars['themeL10n'] = \Lang::get('my_plugin::lang.theme');
...
});
In componentA.htm:
<div> {{themeL10n.welcome_message}} </div>
This approach kept templates clean and flexible, allowing for easy updates to static content in each language without altering the structure of the templates/components. If we needed to add another language, it was as simple as copying and translating the lang.php file.
RTL Considerations in the Frontend
Managing Right-to-Left (RTL) languages, like Arabic, required special handling in the frontend. While RainLab Translate took care of language switching, additional adjustments were needed to ensure the RTL version of the site was visually consistent with the Left-to-Right (LTR) version.
Font Size and Spacing Adjustments
Arabic text tends to appear denser than English, requiring adjustments to font sizes and spacing. Slightly increasing the font size and adjusting padding and margins ensured that the RTL version of the site looked visually harmonious compared to its LTR counterpart. These adjustments were applied via the lang attribute in the HTML tag:
<html lang="{{lang}}">
...
</html>
In *.css:
html[lang='ar'] #welcomeMessage {
font-size: 50px;
}
Each case was handled individually, as mass adjustments could negatively impact the overall layout of the site in an unforeseeable way.
SCSS and Custom RTL Mixins
To manage RTL-specific styles, we leveraged SCSS and created custom mixins that helped manage the styling switches from LTR to RTL.
For instance, this ‘rtl-image-rotate-x’ mixin allows us to flip an image for RTL:
@mixin rtl-image-rotate-x() {
transform: rotateY(180deg);
}
img{
width: 23px;
height: 20px;
body:dir(rtl) & {
@include rtl-image-rotate-x();
}
}
Mixins reduced manual errors and ensured consistency across the site. It allowed us to easily debug any styling issues by updating a single mixin rather than looking through every rule set.
Conclusion
October CMS v2 and the RainLab Translate plugin enabled us to effectively manage multilingual content and RTL support across microsites. By developing a custom LocaleSetter component and utilizing SCSS mixins for RTL styling, we created a flexible and scalable system that handled both language switching and regional distinctions seamlessly. This approach ensured that both LTR and RTL versions of the site were visually consistent, polished, and easy to maintain, reducing the complexity of managing multiple locales within a single codebase.
Check out the RainLab.Translate documentation for more information, and make sure to check out https://talk.octobercms.com/ for your own custom needs. Stay tuned for future updates from us on October CMS!