Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

davorminchorov's avatar

Building Multilingual Apps Best Practices

Hello!

I am looking for some tips, tricks and approaches about building multilingual applications.

  • How do you go about separating the content in each language? I am talking about general stuff (application design, database tables, URIs, Classes/Files etc), not a package.

Let's say that I have to build a blog. I wanna make it something like, click on a flag (link/button) and the website translates into that language!

  • How would I separate the content for each language?

I am not really looking for more than 2 languages for now (Macedonian and English) but having an option to extend the application into more languages is not bad at all!

If you have any interesting articles or videos, feel free to share them here!

Thanks

0 likes
24 replies
florianbeer's avatar

I approached this once when building a webshop system with Laravel.

  • Set your default and fallback locale in app/config/app.php
  • Set the 'languages' array to your chosen languages, e.g. 'languages' => ['en' => 'English', 'de' => 'Deutsch'] for my app
  • Set up a route to switch languages in routes.php:
Route::get('switchLanguage/{lang}', ['uses' => 'ShopController'@switchLanguage', 'as' => 'switchLanguage']);
  • In my controller method I set a global Session variable with the chosen language
    /** 
     * @param String $lang
     * @return mixed
     */
    public function switchLanguage($lang)
    {   
        Session::put('language', $lang);
        return Redirect::back();
    }
  • Then I implemented a route filter to set the language:
Route::before(function()
{
  // Set default session language if none is set
  if(!Session::has('language'))
  {
      // detect browser language
      $headerlang = Request::server('http_accept_language');
      if(isset($headerlang))
      {   
          $headerlang = substr(Request::server('http_accept_language'), 0, 2); 

          if(array_key_exists($headerlang, Config::get('app.languages')))
          {   
              // browser lang is supported, use it
              $lang = $headerlang;
          }   
          // use default application lang
          else
          {   
              $lang = Config::get('app.locale');
          }   
      }   
      // use default
      else
      {   
        // use default application lang
        $lang = Config::get('app.locale');
      }   

      // set application language for that user
      Session::put('language', $lang);
      App::setLocale(Session::get('language'));
  }
  // session is available
  else
  {
      // set application to session lang
      App::setLocale(Session::get('language'));
  }
});

Now you just have to set up the corresponding language files in app/lang/{$lang} and translate all your strings (I made one file per module/section for my app). Whenever you echo out anything predefined in one of your views, you use Laravel's Lang Facade to get the correct string. E.g. {{ Lang::get('admin.settings') }} to show the translated string for "Settings".

keevitaja's avatar

How about you just remove the locale slug from the REQUEST_URI in the index.php file?

$pattern = '/^\/(en|fr)\//';
$uri = filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);

if (preg_match($pattern, $uri, $matches)) {
    $uri = preg_replace($pattern, '/', $uri);

    $_SERVER['REQUEST_URI'] = $uri;

    define('LOCALE', $matches[1]);
}

I just wrote a package and a blog post. So i am bit spammy again

http://keevitaja.com/2015/07/multilingual-laravel-applications-right-way/

davorminchorov's avatar

Cool stuff! Thanks!

What about the content? Should I keep it in the same table with a language field or language_Id(foreign key) from a language table?

keevitaja's avatar

I would go with multiple tables. The main table + one for fields. One to Many relationship.

But i am just guessing. I have not done a multilingual project with dynamic translatable content. Check out AsgardCMS or OctoberCMS, Not sure though what approach are they using there.... At least you get some ideas.

Edit: If you are building a CMS then please note, that people probably need an easy solution to add fields. In this case my recommendation for two table solution and AsgardCMS is a bad example. You would need something like PyroCMS Streams Platform but with multilingual support.

davorminchorov's avatar

Nah, I am just asking about general apps, not really planning on building Just Another CMS, Just curious how to deal with more than one language in an application.

tomicakorac's avatar

@Snapey that's a great link. Thank you very much for sharing. I'm very happy to see that it addresses some of the most frustrating (and coincidentally most common) mistakes of localized web sites, such as adding flags to symbolize languages.

And a very interesting topic in general this is. If I may hijack it with just one question, I'd like to know what's good practice to localize actual meta-data for a model? For example, here are a few links explaining best practice for database schema for localized apps. But what I'm missing is where do I store translated strings for, let's say, field labels (blog post title, blog post body) and translations of the model names (Blog post)?

luddinus's avatar

@tomicakorac I always store that values in the same table with a prefix of the language (title_en, title_fr...), and then a trait or a helper like this

function ml_value($model, $field)
{
    return $model->{$field.'_'.$current_language};
}
davorminchorov's avatar

That will make you duplicate fields for each language you add to the application!

I was thinking about putting all of the content in one table (let's say posts) and add a language_id field to all tables (or almost all tables) and load content by language based on the request. I am not sure what this approach will do in the long run, what the downside might be.

tomicakorac's avatar

@luddinus I'm not sure that's a good idea. If for example I have a table called posts_en with columns title, body, featured_image, that's not a table where one should add columns like content_type_name, content_type_description etc.

luddinus's avatar

@Ruffles I don't understand. Duplicate fields? One for each language... where would you save the content?

@tomicakorac Why would you have only posts_en table? The POST would be always the same (same ID, created_at, updated_at...).

Maybe posts is not the best example, what a about a NEW?

davorminchorov's avatar

I was thinking more like this:

posts table

ID | Title | body | slug | language_id | created_at | published_at | updated_at | deleted_at |
----------------------------------------------------------------------------------------------------------------------------------
1 | This is my title | This is my body | 1 // english
-----------------------------------------------------------------------------------------------------------------------------------
2 | Spanish Title | Spanish Body | 2 // spanish 
------------------------------------------------------------------------------------------------------------------------------------
luddinus's avatar

@Ruffles And relationships? You said "duplicated fields" in my approach. In your's I see duplicated ROWS...

davorminchorov's avatar

One to One between posts and languages, because each post is kind of unique and can only be written in one language.

zoransa's avatar

Check this package out!!!!

It is utterly awesome: "dimsav/laravel-translatable"

keevitaja's avatar

offtopic:

why this topic does not allow "tumbs up"?

edit:

on the second page it is possible. is it just me or anyone else too?

1 like
tomicakorac's avatar

@keevitaja same here.

@Ruffles yeah, that's also one of the most common schemas, but the principle is more or less the same. My point is that I agree with you, translation for meta-data of a certain model (such as Product, or Blog post) should not be placed inside that model's table (products, or blog_posts), like @luddinus suggested. But I am not sure where it should go.

I know such data is often stored in the lang files, or in config files. That's not a bad idea, since it provides a mechanism for multi-language pluralization, which is a very important problem, as I'm sure you know. Another, more complex, problem is multi-language difference in morphology (declination and conjugation) of most non-english languages, which I'm only guessing would be more easily solved by storing meta-data in a .php file instead of a database. This is also what @Snapey's link suggests.

However, I do believe that storing meta-data needs to be in a database because it provides better control for users (specifically, easier to add/edit/delete content types). Also, although solving most problems that I've mentioned would be easier if meta-data is translated in a file instead of the database, I'm sure it's not impossible in the opposite case. Just a tag bit more work, that's all.

Snapey's avatar

If you are talking about a CMS type scenario then the title and body are not what I would call meta-data. Meta-data is data about data. The post and the title are the actual data, the bits the user does not see (ID, userID, date created, tags, deleted, publish date etc are all meta-data.

If publishing a multi-lingual blog, you have two choices, ask the user to enter data in each language (ie write the article twice) or use a service like google translate to perform an on the fly conversion.

In the case of translating in advance, I would have a posts table (with the post meta data) and then a posts-content table.

The posts content table might have columns of id, post_id, title, body, slug, excerpt, language

Then just store title and body, excerpt etc and a link back to the original article (post_id) and a country code for the language the data is in. It is then possible to add more languages without needing to add more columns to the model.

tomicakorac's avatar

@Snapey just to clarify if we understood each other. Let's say I have a 'Post' data type. A Post may be consisted of a 'post title' and 'post body'. So I may have many posts, each having its own title, and its own body. If the app is multi-lingual, than it means that each post will have its content translated in many languages, for example:

English:
    English title One
        English content One
    English title Two
        English content Two
Spanish:
    Título español Uno
        Contentidos español Uno
    Título español Dos
        Contentidos español Dos

However if I choose Spanish language and try to create a new post, I will still get English taxonomy, like Posts > Create new post > (Post title + Post content). Where should I store translations for such 'data about data type'?

I'm sorry if I created confusion calling it meta-data. I guess I could see meta-data as 'data about data type'. If that's wrong, I take it back. But I'm still not getting the answer about how to structure my database to store translations for a new content type.

EDIT: I'll try to be as specific as I can:

  • What am I creating? A blog post.
  • What is a blog post consisted of? It is consisted of a blog post title and a blog post content.
  • How is a blog post called in Spanish? It is called entrada en el blog.
  • How are blog post title and blog post content called in Spanish? They are called titulo de entrada de blog and contentido de entrada de blog.

The question is: Where do I store spanish values entrada en el blog, titulo de entrada de blog, contentido de entrada de blog?

davorminchorov's avatar

Things like button text, links, labels, static content etc?

I was thinking each language will have a different view and they will be named something like:

lang_create.blade.php
lang_edit.blade.php
lang_something.blade.php

so now, in your controller, you can make a query to get all posts for that language

public function index($language)
{
    // the query
    
    return view("{$language}.posts.{$language}_something"); // $language will contain the 2 letter code like en, es, de, fr etc based on the code from the URI.
}

The folder structure for the views will be like:

resources/views:

- en (language code)
    - posts
        - create.blade.php
        - edit.blade.php
- de
    - posts
        - create.blade.php
- fr
    - posts 
        - edit.blade.php
tomicakorac's avatar

@Ruffles that's a neat idea actually. I'm just afraid that it might be too much of copying practically identical view files to just change a couple of strings. And then what to do when it comes to maintaining it? Modifying a separate view for each of potentially dozens of existing translations sounds very scary already.

What about adding or removing languages? That would require creating/deleting folders and copying/modifying files, so not very practical for the end user.

And what about if I want an option to add a new content type from my back end? I want to create dogs management system, so I want to go to my dashboard > create new content type > Type name: Dog > Type taxonomy: name, race, owner etc. I want to be able to enter this wording in the same way for each existing translation: Dashboard > Create new content type > [English|Spanish|Lilliputanian] > Localized content type > Localized taxonomy names.

Having separate view files for specific languages does sound very useful, and it might be nice to have the option to add a collection of views per translation, and then fall back to the language-neutral views if language-specific views don't exist.

davorminchorov's avatar

You are right, repeating with views all the time. Maybe Config files are not a bad idea after all. Similar to how the validation errors work, you can translate the static content, buttons, links etc. there and load the config files in one view. The content will change based on the locale that is currently set.

tomicakorac's avatar

Yes, that's one option. It allows for easy localized plural forms, and possibly fairly easy declination. The downside is still creating/modifying/removing content types, which would still require fiddling with file system.

Anyone else has a suggestion of how to store content type related data in a database?

Please or to participate in this conversation.