Module Development
- Written by Matthias Mullie on Tuesday 26 July 2011
1. Foreword
In these series of articles we'll try to explain how Fork CMS works and how you can write your own modules. To help us illustrate certain aspects, we'll be using a mini-blog module. This module is a simplified version of the default blog module. You should start by downloading this mini-blog module.
2. Installing Fork CMS
In these series of articles we are going to work on a local server before moving our work to an online server environment. Before downloading, check if your server (both local and online) meets the minimum requirements for Fork CMS. If it does, create a MySQL database and remember the credentials. Next, download the lastest release and unzip the folder.
Point your localhost (e.g. myforksite.local) to the path where you installed Fork. Then, visit http://myforksite.local/install) to start the installation process.
3. Applications
Fork CMS exists out of three applications. The first, perhaps the most important one, is the backend. This is where the content of the website will be created or modified.
3.1 Backend
From a user's perspective, this part of a Fork CMS website can be found at http://myforksite.local/private. It's here where you login with the username and password you entered during the installation.
In the filesystem, everything concerning the backend is located in the /backend folder.
3.2 Frontend
The frontend is where the actual website is located. From a user's perspective the location is of course http://myforksite.local. The files for the webdeveloper however are located in /frontend folder.
3.3 API
A third application, often not used, is the API or Application Programming Interface. With this application you can make your website/application accessible by external websites, applications, ... We'll discuss this in Chapter 22: API.
4. Library
All three Applications make extensive use of the Spoon Library. If you've never worked with Spoon it might be a good idea to browse through the Spoon-documentation, although the Spoon code we'll discuss is pretty self explanatory.
You'll find Spoon in the Library Folder. In this folder, all files are stored which are used by the three applications. You'll find globals.php here too.
If you're using other library's, f.e. to link your website with facebook, twitter, picasa,... it might be a good idea to save them here, in the library/external folder.
5. Routing
One of the strong points of Fork is it's ability to maintain very readable URL's. Not only is this userfriendly, it's very interesting from an SEO point of view too.
5.1 Existing file or folder
When any URL is accessed on your domain, the .htaccess file of Fork CMS is consulted. The most important thing it does is checking if the URL which was opened, is a physical location (file or folder). If this is the case, that location is opened. If the requested url is not an existing file or folder, the routing begins, follow the rules below.
5.2 Language
If the URL's isn't a physical location, a rewrite will occur to /index.php which includes /routing.php. This file makes sure that the correct page is opened.
First, it checks if your website is multilingual, in which case a language indicator will be the first slug in the url. This is something you had to specify while installing Fork CMS.
If your site is multilingual the, first part of the URL is interpreted as a language abbreviation:
- http://myforksite.local/en/mini-blog/detail/fork-cms-ftw
If this is an active language, that language is chosen.
5.3 Page
After determining the language, /routing.php searches in a pages-cachefile (which is saved per language: e.g. cache/navigation/navigation_en.php) generated by Fork CMS for the url-part of the matching a page.
In our case, a search will start for mini-blog/detail/pigs-ftw. If this string is not found, the last slug of the url is omitted (mini-blog/detail/pigs-ftw becomes mini-blog/detail) and the cache file is searched again. This happens over and over until a page is found. If no page is found, the request will return the 404 page.
5.4 Action
If a page is found (mini-blog), the next part of the URL (if any) will be interpreted as an action, (in the case of mini-blog/detail/pigs-ftw, the action will be detail). If this is an existing action in the module linked to the page, that action will be executed. If it doesn't exist, the default action defined in the config-file will be executed.
The action-part in the url (in this case: detail) will map to a matching action-translation. detail is one of the default actions already, but if you're adding a new or uncommon action, don't forget to create a translation for it. Internally, the action-part in the url will be searched for in the translations, and it's reference code will be used to route to the correct action, this way it's possible to even translate that part in the url when dealing with multiple languages.
Example: http://myforksite.local/en/mini-blog/detail/pigs-ftp
Fork CMS will browse through all action-translations for this language (in this case: en) until it finds one that's been translated to detail. Here, it'll find the action translation with reference code Detail, which will cause your detail.php action to be launched.
5.5 Parameters
When an action is selected, the remaining part of the URL is interpreted as a list of parameters. In our case we have only one parameter: pigs-ftw. What will happen if the parameter(s) is valid or not depends completely on how the action is developed.
6. Modeling your module
6.1 What will the module do?
The easiest way to start developing a module is to make your mind up about what the module is supposed to be doing. You'll completely have to separate this for frontend and backend. In our example we'll talk about the frontend. The workflow for the backend would be identical, but we would do completely different things (delete, edit, add, ...) as we'll see later on.
In the frontend of our mini-blog there will be 4 different functionalities:
- View a list of our blogposts (index)
- View the details of one blogpost (detail)
- Get a list of the most recent posts (recent_posts)
- Mark a blogpost as frikkin' awesome (this_is_awesome)
For every functionality you'll create a .php file.
6.2 How will these functionalities work?
After you have decided what your module has to do, you have to decide when these will be executed:
- on pageload (see "how")
- ajax: when the page is already loaded
And how they will be presented
- action: the most important part of a page
- widget: just a smaller part of the page
Depending on these choices, you'll place your .php files in either the actions, ajax or widget folder.
As discussed earlier, actions are url-aware. When a module is linked to a page, the exact action that will be executed depends upon the url.
Given our previous example:
- http://myforksite.local/en/mini-blog has no action defined and will load the default action (in this case the list of our examples: index.php)
- http://myforksite.local/en/mini-blog/detail/fork-cms-ftw defines an action detail, so detail.php will be loaded. It is up to detail.php to determine what to do with the remaining fork-cms-ftw in the url.
For this reason, only 1 module can be linked to a page whereas several widgets (which are url-independent) can be linked to 1 page. Usually, widgets are lesser important parts that exist mainly to inform or attract users to actions.
In a blog module, the blogpost-overview and blogposts themselves obviously should be actions, whereas a widget could be a tiny part displaying the titles of the 3 latest blogposts. Such widget could be added to any page in the sidebar, the footer, ...
The this_is_awesome action will be activated by clicking a button. This will add 1 to the awesomeness column of the record of a specific blog post. We will then hide that button, and update a counter on the page. Because the complete page will not to be reloaded, this is a typical AJAX-action.
Note: You could of course be adding comments with Ajax too.
6.3 Defining your module in the database
Before you can use the module, some records needs to be added to the database. Typically this is done by the installer you'll write at the end of this manual, but here's an overview already, so you can start developing your module.
6.3.1 modules
In this table, you add the module name. Don't use camelCasing when giving the name, but the exact representation of the folder containing your module's code. mini_blog is what we add.
6.3.2 modules_extras
The records in this table describe the different blocks and widgets we can add to our pages.
If the different actions of your module will be using the same page layout you can add a record which in our case looks something like:
| Column | Value | Additional information |
| module | mini_blog | The module name |
| type | block | Either block or widget, depending on the choices made above |
| label | MiniBlog | The label used to display the name of this page extra, this will call lblMiniBlog - more on that in Chapter 10: Translations |
| action | NULL |
Insert the exact name of the action (e.g. index, detail) if you only want this exact action to be shown when this is linked to a page (generally not advised, as every action will then have to be linked to another page) |
Should you want to add one specific action as a separate block - perhaps because the page layout (sidebar, footer, ...) is completely different from other actions so another template and thus another page must be used - you add the same record, but with the action field filled in, e.g. detail.
For our widget we need to add a record like this:
| Column | Value |
| module | mini_blog |
| type | widget |
| label | RecentPosts |
| action | recent_posts |
6.3.3 groups_rights_modules
By default, only one group is installed (1: admin). If you want to give this group access to the module in the backend, you need to add a record for the module in this table.
Note: the user that installed Fork CMS is considered the God-user. This user always has all permissions. These rights are used for other users to determine if they have access to modules and actions in the backend. Rather than setting these rights directly into the database, we strongly recommend using the built-in Group Permissions tool (located at http://myforksite.local/private/en/groups/index) to set the correct privileges for your users, otherwise they will not be able to manage the module.
6.3.4 groups_rights_actions
When you've given a group the rights for the module, you will need to add a record for each backend-action that the group must be able to use. In our case, we'll have an add, edit, delete and index action.
You'll see a field level in this table which you always must fill in with 7 (inspired by read, write & excecute in the UNIX filesystem)
Note: as described above, we strongly recommend using the built-in Group Permissions tool over manually adding the permissions in the database.
6.4 The visible parts of the module
The action- & widget-files are controllers that will handle and manipulate the data, but do not contain any visual representation. For every action that has a visual representation we need to create a template.
As you might have already noticed, all the actions except the this_is_awesome action have a template-file (.tpl). They can all be found in the layout/templates folder, except the template for the recent_posts widget. This can be found in the layout/widgets folder.
Templates files are regular .html-files but contain placeholders for the data the actionfiles will fetch from the database. More about these templates we'll see in Chapter 9: Templates.
When browsing (e.g. to http://myforksite.local/en/mini-blog/detail/fork-cms-ftw) the same routing conventions will be used to select the correct template. The template should therefor always have the same name as the action/widget file, followed by ".tpl"
If for any reason, you need to use a template that does not follow these naming conventions, it is possible to override this behaviour in the called-upon action/widget.
6.5 Config
In the root folder of every module, there will be a config.php. This is the starting point when the module is executed. Here you define which action is the default action (in case of no action is provided in the URL as we saw in the routing chapter), and which actions should be disabled.
6.6 Backend navigation
For the frontend, you alter the complete navigation by dragging the page to the right places on the “pages”-page in the backend.
For the backend however, when you write a module, you need to add the backend navigation that is used to edit the module in the /backend/cache/navigation/navigation.php. Actually, inserting the module into the backend navigation will usually be handled in the installer, which we'll get to in Chapter 24: Creating an installer.
In this file, you'll find a multidimensional array representing the complete backend navigation. For more information check out the file itself.
For the mini-blog module, you'll want to add the following array to the element “children” of the array with the label “Modules”, after which the pages will become visible on the “Modules” page.
array(
'url' => 'mini_blog/index',
'label' => 'MiniBlog',
'children' => array(
array(
'url' => 'mini_blog/index',
'label' => 'Articles',
'selected_for' => array(
'mini_blog/add',
'mini_blog/edit'
)
)
)
)
7 Writing your module
7.1 Autoloading
Before we start writing our first lines of code, a word about the naming of your files, classes, labels, ... If you name all your files well, you'll never have to include, require or embed a single file, Fork CMS will do this for you.
7.1.1 camelCasing
All your classes, variables, id's... in your php, js and css-files, should be named using camelCasing. Names will exists only out of letters and numbers (there are no spaces) but every new word starts with an uppercase letter. Some examples:
- new message becomes newMessage
- a great class becomes aGreatClass
- variable becomes variable
- mini blog becomes miniBlog
Your files and tables & columns in the database however, should never be written in camelCasing, but always in lowercase and an underscore instead of a space. The examples above become:
- new message becomes new_message
- a great class becomes a_great_class
- variable becomes variable
- mini blog becomes mini_blog
7.1.2 Javascript
Not only will your .php files be autoloaded, you can autoload js-files too. There are two possibilities.
You name your file like the module, e.g. mini_blog.js and put it in the js-folder in your module folder. This way, the js-file will be included on every page of the module. This is what we're doing for our mini-blog module because we need the same javascript code on the detail and the index page.
Should a certain peace of js-code only be used on one action, then you give it the name of that action, e.g. detail.js.
7.1.3 Templates
As you have have seen in our example, the module folder contains a layout folder which contains the templates and widgets to display the pages. These templates can be considered a default. When a new theme is applied to the website, the template for every module can be overwritten to better match the theme's style.
To avoid altering files in the module folder (so you can re-use it later without having to think about altered code), you can create your own theme which will have a folder in the themes folder: /frontend/themes/the_a_theme/modules/. Here you put a folder with the module name (mini_blog) which contains the templates, widgets, css, ... folders, which in turn contain the custom files.
When Fork CMS opens a webpage, it first searches for the requested template in the /frontend/themes/<selected_theme_in_fork_cms>/modules/modulename/layout/templates folder. If a template with the correct name (e.g. detail.tpl) is not found, Fork CMS falls back to the template in the default folder, frontend/modules/modulename/layout/templates.
7.2 Model
As you perhaps have noticed, the only PHP-file we didn't cover in the previous chapter is the model.php file, perhaps the most important one.
This file contains all the generic functions, most of the time functions that make a connection to the database.
A rule of thumb should be that every line of (parametrized!) SQL you'll write, has to be written in a model.php file.
Beneath you'll find some lines of the model.php file of the mini blog module. We'll use this example to illustrate some conventions.
class FrontendMiniBlogModel implements FrontendTagsInterface
{
...
/**
* Fetch one article based upon it's meta url
*
* @return array
* @param string $URL The URL for the item.
*/
public static function get($URL)
{
return (array) FrontendModel::getDB()->getRecord('SELECT i.id, i.language, i.title, i.introduction, i.text,
UNIX_TIMESTAMP(i.edited) AS edited, i.user_id,
m.keywords AS meta_keywords, m.keywords_overwrite AS meta_keywords_overwrite,
m.description AS meta_description, m.description_overwrite AS meta_description_overwrite,
m.title AS meta_title, m.title_overwrite AS meta_title_overwrite, m.url
FROM mini_blog AS i
INNER JOIN meta AS m ON i.meta_id = m.id
WHERE i.language = ? AND i.publish = ? AND m.url = ?
LIMIT 1',
array(FRONTEND_LANGUAGE, 'Y', (string) $URL));
}
... class continues for another 200 lines
}
First we encounter the naming of the class. We can split this in three parts. Mind that classnames always begin with an uppercase and the rest follows the general in camelCasing.
- The application, because we'll have a Backend model too (Frontend)
- The module (MiniBlog)
- The action/name of the file (Model)
It can be a good idea to write your model.php first. The first thing you have to do is figure out what exactly every action has to be able to do.
- detail
- get all the details of one article
- get the previous and next article (for navigation)
- index
- get a list of items, ordered by date, grouped by a given number and offset
- get the total amount of articles (for navigation)
- recent_posts
- get a list of items, ordered by date, grouped by a given number
- this_is_awesome
- check if an article exists
- add 1 to the number of reports of the article
As you are a programmer you'll probably have seen that we want to fetch a list of items twice, in almost the same way. Instead of writing this code twice for both actions, we write it in model.php where it's shared between the actions (and even other modules.)
8 Blocks and widgets
8.1 Adding pages
8.2 What's the difference?
Blocks and widgets are the visible parts of your websites. The blocks, you'll find in the actions folder, the widgets you'll find in the widgets folder. Their operation is exactly the same with two differences:
- There can only be one “block” on a page.
- Because of this, each block has an URL (that can be fetched using the function getUrlForBlock)
This block can be a module, with every action included, or just one action of a module.
As long as the selected template allows it, there can be as many widgets as you want on a page. The structure of a widget is (most of the time) less complicated than a block because they are used merely for displaying data.
8.3 Structure
When you check out other existing Fork modules, you will see that most actions use the same structure, using the same method names. Beneath you'll find the complete code of the detail-action of our mini blog.
class FrontendMiniBlogDetail extends FrontendBaseBlock
{
Again the classname needs the be exact ApplicationModuleAction, in our case FrontendMiniBlogDetail. Because our action is a Block, it extends FrontendBaseBlock. This class takes care of everything concerning URL-handling, breadcrumbs, ...
/**
* The blogpost
*
* @vararray
*/
private $record;
Then we define our (private) variables. In our case we'll use an array to save the record with the article we will be viewing.
public function execute()
{
// call the parent
parent::execute();
// hide contenTitle, in the template the title is wrapped with an inverse-option
$this->tpl->assign('hideContentTitle', true);
// load template
$this->loadTemplate();
// load the data
$this->getData();
// parse
$this->parse();
}
The execute function is always present and is called by Fork CMS when opening any action. As you can see, the execute method of FrontBaseBlock is called too. This makes sure that the js-files and css-files are autoloaded.
The line starting with "$this->tpl->assign(" ... assigns a variable to the template we'll be using to display the action.
loadTemplate (also defined in FrontBaseBlock) loads the template file in which we parse the data we'll be loading in our self defined method getData.
/*
* Load the data
*
* @return void
*/
private function getData()
{
// if no parameter was passed we redirect to the 404-page
if($this->URL->getParameter(1) === null) $this->redirect(FrontendNavigation::getURL(404));
// get the record, or at least try it
$this->record = FrontendMiniBlogModel::get($this->URL->getParameter(1));
// if the record is empty it is an invalid one, so redirect to the 404-page
if(empty($this->record)) $this->redirect(FrontendNavigation::getURL(404));
// add some extra info to the record
$this->record['full_url'] = FrontendNavigation::getURLForBlock('mini_blog' , 'detail') . '/' . $this->record['url'];
$this->record['tags'] = FrontendTagsModel::getForItem('mini_blog' , $this->record['id']);
}
The getData function first checks if the item given in the URL exists and adds some extra data which will be used in the template, or redirects to a 404-page if it doesn't. (The 404 page is installed by default when installing Fork).
If an article was found, the data we fetched is parsed into the template-file.
private function parse()
{
$this->breadcrumb->addElement($this->record['title']);
$this->header->setPageTitle($this->record['title']);
$this->header->setMetaDescription($this->record['meta_description'] , ($this->record['meta_description_overwrite'] == 'Y'));
$this->header->setMetaKeywords($this->record['meta_keywords'] , ($this->record['meta_keywords_overwrite'] == 'Y'));
$this->tpl->assign('item', $this->record);
$this->tpl->assign('navigation' , FrontendMiniBlogModel::getNavigation($this->record['id']));
}
}
As you can see, it's fairly easy to add an item to Fork's breadcrumb object and to add the meta-data to the <head> of the page. We discuss both objects later on.
9 Templates
9.1 Variables
To make our different pages visible on our website we need to write a template file for every action. As said before, a template file is a html-file containing placeholders for the data that we will parse into that template-file.
Let's first have look at the detail-page, in the default Fork CMS-theme, in the browser:
The code that was used in the .tpl-file you find below. As you can see it's regular html-code, but with a lot of data between { and }. We'll discuss the different types of placeholders one by one.
<div id="blogDetail">
<article class="mod article">
<div class="inner">
<header class="hd">
<h1>{$item.title}</h1>
<ul>
<li>{$msgWrittenBy|ucfirst|sprintf:{$item.user_id| usersetting:'nickname'}} {$lblOn} {$item.edited|date:{$dateFormatLong}:{$LANGUAGE}}</li>
</ul>
</header>
<div class="bd content">
{$item.introduction}
</div>
<div class="bd content">
{$item.text}
</div>
<div class="awesome" id="awesome{$item.id}">
<span class="counter">{$item.awesomeness}</span>
{$lblPeopleThinkThisPostIsAwesome}
<span class="bar">|</span>
<a class="add" rel="{$item.id}">{$lblIThinkThisIsAwesome}</a>
<span style="display:none;" class="added">{$lblIThinkThisIsAwesome}</span>
</div>
<footer class="ft">
<ul class="pageNavigation">
{option:navigation.previous}
<li class="previousLink">
<a href="{$navigation.previous.url}" rel="prev">{$lblPreviousArticle|ucfirst}: {$navigation.previous.title}</a>
</li>
{/option:navigation.previous}
{option:navigation.next}
<li class="nextLink">
<a href="{$navigation.next.url}" rel="next">{$lblNextArticle|ucfirst}: {$navigation.next.title}</a>
</li>
{/option:navigation.next}
</ul>
</footer>
</div>
</article>
</div>
When we discussed the detail.php file we came across the following line:
$this->tpl->assign('item', $this->record);
With this code, the array in $this->record, containing our article, is parsed into the template as the array item. The syntax for reading elements from an array in a template is the dot-syntax, preceding the variable name with an $. On the sixth line your see {$item.title}. This does nothing more than echoing the value in the field 'title' of our array 'item'.
In some cases, you'll have array's in array's, in array's, in ... you'll write them next to each other as in our example: {$navigation.previous.title}
9.2 Locale
When you're developing a module you'd better don't write plain English, Dutch, French, ... in your templates. Instead you use locale which will be replaced by the right translation. You see a couple of locale in our example:
- {$msgWrittenBy}
- {$lblOn}
- {$lblPeopleThinkThisPostIsAwesome}
Everything what applies to normal data applies to labels as well. They stand between { and }, are preceded by $ and can be used in combination with modifiers (see next chapter). There are two differences. The first is that you don't have to parse them in the template yourself, this happens automatically.
The second is that labels always begin with lbl, msg, act or err. More about this, you'll find in the next chapter.
9.3 Modifiers
Because plain simple translations or only echoing data is not always what we need, you can add modifiers to an echo of data or a label. Take this example:
The variable {$item.edited}, echoed without modifiers would look something like:
... a UNIX-timestamp.
We put a pipe next to the variable name, followed by the modifier name and next you'll find two arguments, separated by colons. {$dateFormatLong} and {$LANGUAGE}, two other variables automatically parsed into the template by Fork. The result of this expression becomes
It's possible to send the result of one modifier to another modifier, just add a pipe and write the next modifier.
9.4 Commenting
Templates have other possibilities too. To illustrate these we'll use the template of the recents_posts widget.
As you see on the first 4 lines, we added some comments. When you're working with different people on a website it might be a good idea to provide on overview of which variables are parsed into the template.
{*
variables that are available:
- {$widgetMiniBlogRecentPosts}
*}
<section id="blogRecentCommentsWidget" class="mod">
<div class="inner">
<header class="hd">
<h3>{$lblRecentArticles|ucfirst}</h3>
</header>
<div class="bd content">
{option:widgetMiniBlogRecentPosts}
<ul>
{iteration:widgetMiniBlogRecentPosts}
<li>
<a href="{$widgetMiniBlogRecentPosts.full_url}">{$widgetMiniBlogRecentPosts.title}</a>
{$msgWrittenBy|ucfirst|sprintf: {$widgetMiniBlogRecentPosts.user_id|usersetting:'nickname'}} {$lblOn}
{$widgetMiniBlogRecentPosts.edited|date:{$dateFormatShort}:{$LANGUAGE}}
</li>
{/iteration:widgetMiniBlogRecentPosts}
</ul>
{/option:widgetMiniBlogRecentPosts}
{option:!widgetMiniBlogRecentPosts}
{$msgThereAreNoRecentItemsYet}
{/option:!widgetMiniBlogRecentPosts}
</div>
</div>
</section>
9.5 Options
Templates have two programming techniques. The first are options. They work just the same as an if-statement, except the only thing you can check is: Is a variable set, or not set.
In our example above we check if the variable widgetMiniBlogRecentPosts isset. If so, the code between the options is show. Else, it's not shown!
You add the “Else” yourself by reversing the same option with an exclamation mark. This way you can add some kind of error message.
9.6 Iterations
The second programming technique your can use in templates are iterations. With iterations, you can walk all the items of an array. This example prints all the title fields of an array.
{iteration:widgetMiniBlogRecentPosts}
{$widgetMiniBlogRecentPosts.title}
{/iteration:widgetMiniBlogRecentPosts}
There are a lot of other things you should know about iterations, such as nesting iterations, the first- and last-option, cycle, ... You'll find everything about these on the Cheatsheet.
9.7 Includes
Another nice thing about templates is that you can include other templates, just like you do in .php.
E.g. in the backend, you will add the following lines to every action template so all pages start with the same header:
{include:{$BACKEND_CORE_PATH}/layout/templates/head.tpl}
{include:{$BACKEND_CORE_PATH}/layout/templates/structure_start_module.tpl}
The same way, you can add your own pieces of code that perhaps often return on other pages, without it's necessary to create a widget. E.g. a collection of "Share on twitter, facebook, netlog, myspace, ..." buttons.
10 Translations/Locale
Most websites you'll make will be available in different languages. Creating a separate website for each language is a waste of time, that's why Fork CMS has the Locale Module. As we saw in the previous chapter, we'll use placeholders in the templates to display the right text depending on what's the active language. This text has to be added in the database, which you'll do in the backend, under settings, translations.
10.1 Types
10.1.1 Labels
Labels are used the provide a short literal translation. E.g.:
BlogCategories => blog categories
10.1.2 Messages
Messages provide translations for longer texts. E.g.:
NoItems => there are no blog posts yet. You can add one by clicking the add button above.
10.1.3 Actions
Actions are keywords that occur in the url (and as such are confined within a smaller set of characters: e.g. a questionmark is a no-go, since in url's it identifies the start of the querystring)
An example of an action would be:
Detail => detail
10.1.4 Errors
When you want to display error messages, use this type of translation. E.g.:
FieldIsRequired => This is a required field.
10.2 Adding translations
As we said before, translations are added/edited in the backend. Here you see a screenshot of the input form:
The reference code is the name of the translation (Add, NoItems, ...). This code is always written in camelCasing with an uppercase first letter. This is because we'll be using a prefix depending on the type of translation when we're calling the translation in the templates.
labels => {$lblNameOfTheTranslation}
messages => {$msgNameOfTheTranslation}
actions => {$actNameOfTheTranslation}
errors => {$errNameOfTheTranslation}
As you see, now it's “real” camelCasing.
The translation is the text that has to be displayed, depending on which language is used when viewing the website.
You'll have to select an Application too, Backend or Frontend. You select backend when you want to add a translation to the backend pages of your module. The translations of backend and frontend are saved in different files.
When you selected Frontend in the Application dropdown, you'll see you can only select core in the “module”-dropdown.
When adding translations for the backend however, you can select all the existing modules. This way, you can add different meanings for one Reference code:
| module | reference code | translation |
| blog | NoItems | no blog posts yet |
| mini_blog | NoItems | no articles yet |
| news | NoItems | no news yet |
| core | NoItems | no items yet |
When editing a page, Fork CMS searches for a module specific translation, e.g. “no blog posts yet”. When the module specific translation was not supplied, Fork CMS searches for the “NoItems” translation for the “core” module, “no items yet” in our case.
Then, you just need to add the type of translation.
