Building multi language website

edited February 2011 in News & Announcements
Hi,

just out of curiosity. I would like to build a multi language site with FuelCMS soon and I am thinking what could be the best practise in building it and still being able to use CMS backend. It could be good to have possibility to edit pages in different languages. So far I have an idea to build a custom model together with view, which will load the appropriate page according some "language" key in the table.

Any better ideas?
«1

Comments

  • edited 11:11PM
    I was actually chatting with a user about the best way to do it last night but we were just throwing out ideas. I think your idea would work and would be very interested in how it turns out. I'll send him a note to see if he wants to chime in because he too was interested in this topic.
  • edited 11:11PM
    There are a couple of multi-language modules for wordpress that are pretty slick, you could do a base install of those and play around with them for inspiration? If you got a "version 1" going with basic functionality it would be easy to iterate.

    The common theme seems to be to create pages in the base language, then add 'versions' of the page for other given languages. This allows you to add translations for pages you have, and not those you dont. My experience is that you dont get 100% of the site translated on day one. Since you already store versions and cache the live one, you just need to be able to manage multiple versions at once and publish them.

    The good modules also allow you to change the Meta / Navigation / Breadcrumb / Slug for the page too. The one i've used I have the whole site in English, then added a French translation for certain pages under site.com/fr/path/to/page.

    Version 1.1 could include auto-deciphering browser language and a drop-down on the page which sets the user's default language and stores in session / cookie.

    Further, I did use the concept of "culture" in one project, because en_US might actually contain different content and approach to en_GB... Sites like microsoft and apple change their approach based on region.
  • edited 11:11PM
    Thanks for the insight and suggestions.
  • edited 11:11PM
    I also want to extend fuel cms to an multilingual cms.

    The easiest part i guess, is to add to the fuel_page_variables table a "langID" row. after that i extend the pages controller and add a multi_lang controller. (pages contains a dropdown with all languages, onselect the page will refresh with the new languageID. This will guarantee me a global translation for all fields.
  • edited 11:11PM
    Instead of the fuel_pagevariables table, what about adding it to the fuel_page table 'lang_id'? The fuel_pagevariables has a foreign key to that. Then make a compound key between location and lang_id to ensure uniqueness.
  • edited 11:11PM
    i dont get it. a lang_id row to the fuel_pages? in that case, i cant update my dynamic layout fields...
  • edited 11:11PM
    I meant to add a lang_id field to the fuel_pages table and not a row. It's just an idea though and haven't done it locally yet.
  • edited March 2011
    okay, i did it. ;)

    its a bit of work, but with your great logic not that hard.

    // config
    i've created a new config file with an array (array(0=>array('id'=>0, 'langCode=>'en' ...)))

    // library
    i've created a autoload library with some functions (get_current_lang, set_lang, create_lang_chooser ....) and a helper with some short-tag functions.

    //pages
    i extended the table "fuel_page_variables" with one more field, named "lang_id".

    the page controller has a select-dropdown with all languages. on select, the page is reloading and the edit() function will load all entries from fuel_page_variables (where id AND lang_id). so for every entry i create a own lang entry. (thats perfekt, so i have a global multilang-modular system. :)

    //tricky: the multilang navigation/breadcrumb etc.
    i extended the table "navigation" with one more field named "opt_label".
    its stores a serialized array with all other language-labels. (if present). therefor i extended the navigation modul (create, edit, _form function). now, if i edit a navigation item, there will be a input field for each language (btw, it's a pity, that we cant add a fieldset with you absolutely unbelievable great form_builder - :) ).

    at least i extended the menu.php with some functions to get the right label (check which language is currently needed -> check if that id is in the serialized array from the opt_label field -> if yes -> replace it with the label info.
    also i had to check the _create_link function to extend the links with (site/en/about/)
    -> for that i needed by the way a new function, which i placed in your url_helper:


    function is_admin_area() { $CI =& get_instance(); $fuel_path = $CI->config->item('fuel_path', 'fuel'); $fuel_path = substr($fuel_path , 0,-1); if($CI->uri->segment(1) == $fuel_path) { return TRUE; } else { return FALSE; } }

    because the _create_link function is also used in the adminsection, and there we don't need those edits.

    Thank you,

    (sorry for my bad english | greetings from a small guy from austria)
  • edited 11:11PM
    That's great to hear!... nice work. One thing I thought of that my help with the navigation, what if you have each language be a different navigation group and then in the code you just reference that group?
  • edited 11:11PM
    yea... also possible.

    (btw, fuel cms does already have a admincheck like my function above. (IN_FUEL_ADMIN)

    ...
  • edited 11:11PM
    also there is another question: for the frontend we need some default language values.

    please correct me:

    in page_router.php after the check if cms or frontend i will load a languagefile
    $this->lang->load('default', $this->multilang->get_current_langID);

    $this->multilang->get_current_langID is the function from my new multilang class, which checks the url (/en/, /de/...) to return the right language file.

    and now im able to load in every view my right language value $this->lang->line('hello world');

    so i can extend fuel with all his modules with the current languagefile. (for example for the blog line('add_your_comment')

    what do you think about that?
  • edited 11:11PM
    Would a hook work for something like that so you don't have to mess with the page_router controller?
  • edited 11:11PM
    ok. to add a good multilingual option, it's quite impossible to add all that stuff only with hooks. you need to change the extending of the MX_Lang and add a MY_Config to the core.
  • edited 11:11PM
    any news on this front? I too would like to know what's the best way to deal with i18n making use of the backend, is there any available module for this?
  • edited 11:11PM
    Not at the moment. Webcam mentions one method above where a lang_id field was added to the fuel_page_variables table.
  • edited 11:11PM
    I know, i hoped someone had made further progress since the last answer is from march.Ok, so it would really help if someone could try and explain better Webcam's approach, i'm quite new to this and i'm not sure i understand how he did it... especially:

    "the page controller has a select-dropdown with all languages. on select, the page is reloading and the edit() function will load all entries from fuel_page_variables (where id AND lang_id). so for every entry i create a own lang entry."

    it would rock to have some hints on how to do all that... maybe Webcam feels like contributing some snippets ?
  • edited May 2012
    There are several areas you will need to change to get this to work (please chime in webcam if you have any input). I'll take a stab at the pages area as to one possible way to approach it (bear in mind that this is theoretical and I haven't actually implemented it).

    1. Create a config file or table to contain the language options (e.g. $config['languages'] = array('english' => 'English', 'spanish' => 'Spanish')... )
    2. Modify the fuel/modules/fuel/models/pages_model.php file's form_fields method and add the following field. For the options value, substitute in your own language options. The value that will get saved in the fuel_page_variables table will be the key of that array.
    $my_lang_options = $CI->config->item('languages'); $fields['lang_id'] = array('label' => 'Language', 'type' => 'select', 'options' => $my_lang_options);
    3. Add a lang_id value to the fuel_page_variables table
    4. Alter the fuel/modules/fuel/controllers/pages.php controller around line 357 so that the saving array includes the new lang_id value:
    $save = array('page_id' => $id, 'name' => $key, 'value' => $value, 'type' => $val['type'], 'lang_id' => $CI->input->post('lang_id');
    5. Alter the fuel/modules/fuel/assets/js/fuel/controller/PageController.js file so that you attach a change handler to the #lang_id field that will load in the proper fields/values when change the language in the select. This may require a change to the pages controller's layout_fields method so that it includes the lang_id value for pulling in the saved variable values.
    6. Alter the pagevariables_model's _common_query() method to always select from a certain language:
    $CI =& get_instance(); $language = $CI->config->item('language'); $this->db->where(array('lang_id' => $language));

    That covers most of the backend part, but now there needs something on the front end to automatically set the language config value when someone is viewing the site. This part can be done a few different ways and people seem to have opinions as to which is the best way. The main ones being either reading it from a URL or from a cookie/session. If it were from a session, you could set a select dropdown on the front end for a viewer to set their language. Then a controller could set that session variable. You could then set a CI hook to set that language config value value that is used in step 6 above.

    Hope this helps point you along the right path.
  • edited 11:11PM
    wow thanks man, you rock. I will sit down and study the above and hopefully work something out over the weekend. I'll share my findings here soon!
  • edited June 2011
    hey pierlo. in the end, there was a lot of things to do. :)

    You need to consider a lot of things, before you start. such as the page_variables (there must be a lang_id)

    Thats my 'Pages' Site: (in german)

    image

    in my db, i've extended the pages table like that: (also the navigation and blocks table)

    image

    The most important part was to add a new class to the Core (autoload)

    class MY_Lang extends CI_Lang { var $languages = array( 'de' => array('name' => 'german', 'langCode' => 'de', 'id' => 0), 'en' => array('name' => 'english', 'langCode' => 'en', 'id' => 1) ); var $current_lang_id = 0; var $default_lang_id = 0; var $current_segment = '';

    In this Class, there are a lot of other functions, as a foundation you can use the internationalization i18n Class. I extended it with some other functions like "get_admin_lang_chooser", which i'll call it in the module_create_edit_actions view file.

    I've also extended the Base_module_model with two new functions and one new var.

    var: $lang_id = 0; function 1: set_language(); function 2: get_language();

    so we have the possibility, to add multilang features on modules too.

    so... good luck! ;)
  • edited June 2011
    @admin
    I looked at your code..

    First 2 typo corrections for anyone confused:

    $my_lang_options = $CI->config->item('languages'); $save = array('page_id' => $id, 'name' => $key, 'value' => $value, 'type' => $val['type'], 'lang_id' => $this->input->post('lang_id'));
  • edited June 2011
    @admin

    5. Alter the fuel/modules/fuel/assets/js/fuel/controller/PageController.js
    I solved this by triggering change event
    $('#layout').change(function(e){ var path = jqx.config.fuelPath + '/pages/layout_fields/' + $('#layout').val() + '/' + $('#id').val() +'/' + $('#lang_id').val(); $('#layout_vars').load(path, {}, function(){ _this.initSpecialFields(); }); }); $('#lang_id').change(function(e){ $('#layout').trigger('change'); });

    I changed the controller
    function layout_fields($layout, $id = NULL, $lang_id = NULL) { .... if (!empty($id)) { $page_vars = $this->pagevariables_model->find_all_by_page_id($id,$lang_id); $this->form_builder->set_field_values($page_vars); } .... }


    I also changed pagevariables, i set the default lang_id in constructor.
    function find_all_by_page_id($page_id,$lang_id=NULL) { .... if(!empty($lang_id)){ $this->lang_id = $lang_id; }; .... }
    $this->db->where(array('lang_id' => $this->lang_id));
  • edited 11:11PM
    Although this give us somewhat of language support. One main annoyance it doesn't give us location/slug support.
    Maybe i should take a look at webcam's implementation
  • edited 11:11PM
    Thanks for sharing Webcam. Does your solution have location/slug support?
  • edited June 2011
    This is great stuff! Just what I needed right now. I'm a little confused as to what tells the database to create the different sets of page_id based on the lang_id found in the "languages" config file.

    At the moment, changing the lang_id only changes the one page_Id.

    I'm guessing I would need to iterate through the languages I have in my config file and duplicate the layout_fields found in the MY_fuel_layouts.php for every lang_id it finds. That's where I'm lost. Any help would be appreciated.
  • edited 11:11PM
    I believe the page_id stays the same and gets passed to the fuel_page_variables table with the different lang_id, however, the unique index found on the fuel_page_variables table needs to be changed to be page_id, name and lang_id (otherwise it will keep overwriting values with the same page_id and variable name).

    With regards to looping through the MY_fuel_layouts for each language, I think the idea mentioned above recommends using the lang_id dropdown and on change it ajax's in the new page_variable fields specific to the selected language.
  • edited June 2011
    Hey @all.

    @admin: location/slug? you mean at the frontend? such as www.page.com/en/page?
    yes of course.

    at the admin panel, it works mostly with ajax:

    image

    As a example, my navigation page: If i trigger the select, it will open a ajax request to

    "fuel/navigation/lang_fields/2/0" uri_segment(3) => navID uri_segment(4) => langID

    The same happens on my Block Page.
    The first language, which will be loaded (on page load/reload), is always the default lang. So you can start with any language you want.

    Pages Page ( sounds funny)

    There i have to reload the whole page, because of some other features like my parent selector. its also necessary because of the linked navigation items on the right side => every language does have their own linked navigation location.
    So, my pages url looks like this:

    fuel/pages/edit/25/0 uri_segment(3) => pageID uri_segment(4) => langID


    GENERALLY

    During the create method, there is no language select. I will creates automatically the entry with the default language.
    page_variables:

    after the ajax request at the pages page, it will work that easy: get all where('page_id' => 22, 'lang_id' => '1')

    IMPORTANT

    To the fuel_pages, i added a new field called "ref_name". It's necessary because of the page controllers.

    Example: You have a page called "contact". For that page you need a special form, which you can add easily as an controller. just create a file named contact.php and put it into the controllers folder.

    But what if your customer want to call the url "contact" in german "kontakt"? the connection to the controller will get lost. So i added the ref_name, which will setted with the creation of the page. afterwards, that entry will never get changed again.

    hope this helps, guys ;)
  • edited 11:11PM
    Hi admin,

    I changed
    UNIQUE KEY `page_id` (`page_id`,`name`)
    to
    UNIQUE KEY `page_id` (`page_id`,`name`,`lang_id`)

    But the page_variables keep overwriting the old values :( I'm not pro enough to figure out why.
  • edited 11:11PM
    @erik

    you save the page variables thru the page controller with the private _save_page_vars method, right?

    $where = array('page_id' => $id, 'lang_id' => $langID) ;
  • edited 11:11PM
    @webcam

    damn, finally figured it out.. It deletes the old entries 6 lines above :)
    // clear out all other variables $this->pagevariables_model->delete(array('page_id' => $id, 'lang_id' => $this->input->post('lang_id')));
  • edited 11:11PM
    Thank you Erick!!!!!!, your solution along with making 'lang_id' unique is what did if for me. You've saved me a lot of time.
Sign In or Register to comment.