11

I faced this problem several times while building websites. I will explain the using PHP and Laravel as an example but this problem is a common amoung multiple platforms. This was already addressed in a few questions (post1, post2,post3, post4 and some others) but the posts didn't really get a good answer.

The question is: What is the best way of structuring translated content inside of language files?

I'm currently using Laravel (I'm not mentioning the version because both Laravel 4 and Laravel 5 have similar localisation functionalities, at least similar enough for the purpouses of this topic).

The localisation structures the content accross language files (en, es,de, fr...) inside which there can be multiple .php files that contain a return statement that returns a multi-level dictionary structure.

/lang
    /en
        messages.php
    /es
        messages.php

and the files contain something like this:

<?php    
return [

    'example1' => 'example message for value exaple-key',
    'example2' => [
        'sub-example' => 'example message for example1.sub.example',
    ],    
];

and calling of this is done by doing something like this:

//Laravel 5    
trans('messages.example1'); //outputs 'example message for value exaple-key'
trans('messages.example2.sub-example'); //outputs 'example message for example1.sub.example'

//Laravel 4   
Lang::get('messages.example1'); //outputs 'example message for value exaple-key'
Lang::get('messages.example2.sub-example'); //outputs 'example message for example1.sub.example'

A few methods of grouping come to mind:

  1. by website content

    example: homepage.php, page1.php, page2.php...

  2. by logical domain:

    example: auth.php, validation.php, pagination.php...

  3. by html:

    example: buttons.php, popup_messages.php, form_data.php...

  4. by straight traslation:

    example: simple_words.php, phrases.php... and than contain content like 'password-to-short' => 'your password is to long'

  5. Some hybrid/combination of the ones mentioned before

All of these have some obvious benefits and drawbacks and I won't try to go int that but the 5th option is most likely the best solution but there's still the problem of where to draw the line to get minimal duplication of phrases and content.

Annother problem is how to solve the problem of uppercase first characters in some cases and lowercase in other cases as well as punctuation characters at the ends.

I did reaserch regarding this problem but there are no definitive guidelines and/or good examples available to learn from.

All opinions are welcome.

Community
  • 1
  • 1
Traveller
  • 399
  • 2
  • 20
  • Have you considered that the questions you referenced offer no definitive answers probably because there is no definitive solution that encompasses all possible use cases in projects? You've already listed 5 possible solutions, all of which look viable. So just choose the one that fits best with your given project. As for the second part of your question, it can be as simple as using [`ucfirst`](http://php.net/manual/en/function.ucfirst.php) or [`lcfirst`](http://php.net/manual/en/function.lcfirst.php) where needed, or as complex as it needs to be, because again there is no general solution. – Bogdan Nov 05 '15 at 16:25
  • 1
    I m aware that there is no definitive solution. Still, even then, some guidelines and examples for more complex use-cases should exist, or at least a general agreed upon way of doing things. – Traveller Nov 05 '15 at 16:46
  • That was exactly what I was underlining, there is no _"general agreed upon way of doing things"_. The ground work here is provided by [Laravel's Localization](http://laravel.com/docs/5.1/localization), as for the structure, that's up to you and what fits best for your project. Because different applications have different content, suggesting a common structure that fits all is pretty hard, and generally you'll only get opinions for every person, something that [off-topic](http://stackoverflow.com/help/on-topic) here on Stack Overflow. – Bogdan Nov 05 '15 at 16:53
  • Yes, but there still don't exist almost any examples for larger use-cases – Traveller Nov 05 '15 at 17:08
  • 1
    try to use `trans` function also in Laravel 4, I use it to and it work like a charm with something like `trans("menu.labels.users")` I will get the exact value that I use , and `trans("menu.labels")` will return all the values inside the labels array – Zaher Nov 25 '15 at 11:53
  • Thank you, I am aware of that and am using it. I left it this way just because most examples use this format. – Traveller Nov 25 '15 at 11:56

3 Answers3

3

I tend to group functionality in my Laravel apps into self-contained ‘components’. For example, I’ve been working on email campaign functionality for an application recently so put the service provider class, models, service classes in a folder at app/Email.

Bearing this in mind, I organise my translations in a similar fashion. So even though on this project we’re not translating strings, if we were I would create a resources/assets/lang/en/email.php file, and put translated strings for the email component in there.

So in another project, my directory structure might look like this:

  • /resources
    • /lang
      • /en
        • auth.php
        • email.php
        • events.php
        • news.php
        • pagination.php
        • passwords.php
        • validation.php

Hope this helps.

Martin Bean
  • 38,379
  • 25
  • 128
  • 201
  • This is a good structure and I've seen similar things in lang packages but how would you solve the problem of overlapping content? Something that is common to 2 areas. Another problem is when you have to have similar but different content for different context situations (example: admin view, registered user view, visitor view). – Traveller Nov 24 '15 at 12:21
  • Overlapping content: a generic **messages.php** file. Different contexts doesn’t really matter, the string ‘No results found’ would translate the same to another language whether it was in an admin panel or a user’s account. – Martin Bean Nov 24 '15 at 12:26
  • **messages.php** -> wouldn't that file be a really big mess? In my opinion the **messages.php** would end up having important and commonly used content but would be a mess to find something inside it. – Traveller Nov 24 '15 at 12:41
  • regarding the admin/user views. In my previous question I wanted to ask you about the scenario when you have to have slightly different messages for the admin than the user. An example would be login welcome messages (user: you have successfully logged in to your home panel, admin: you have successfully logged in into the system or user: you have successfully logged in, admin: your last login was on YYYY:MM:DD HH:MM:SS) – Traveller Nov 24 '15 at 12:52
  • @Traveller I try to keep my messages as generic as possible to avoid that scenario. Having multiple messages that essentially says, “You have logged in” is a waste of your time and your translators’. And if you’re paying external translators, then that time is also money. – Martin Bean Nov 24 '15 at 12:57
  • I agree but it is often that a client wants these kinds of things (slightly different messages for certain things). That is is why I mentioned it and think it has to be taken into account – Traveller Nov 24 '15 at 13:02
  • @Traveller Well that’s up to your client. But, “You’ve logged in” is a generic message (it doesn’t relate to a particular component) so would go in my generic **messages.php** file as aforementioned. – Martin Bean Nov 24 '15 at 13:04
  • One more question that is also important. How would you structure things inside the specific files? – Traveller Nov 25 '15 at 07:33
  • @Traveller Key–value pairs. No multi-level messages, as that just confuses things. – Martin Bean Nov 25 '15 at 13:22
0

In my experience there is no reason to have different groups other than trying to use your translations somewhere else. I usually put all my project messages in a group named app and for each of my shared libraries I use a separate group name (because I might use them in other projects). An example of a a failure login message in my website would be

trans('app.username_and_password_do_not_match')

and if it's in a third party library named Auth it would be

trans('auth.username_and_password_do_not_match')

And remember to write the full message as your message key instead of using short names (like app.login.fail). this way you don't need to check the website content for every translation.

I didn't fully understand your last problem so you might want to clarify it a bit.

Hossein Shahdoost
  • 1,692
  • 18
  • 32
  • That's fine with short text, simple sentences and phrases. What to do with long content. How to make it reusable and how to find something without always looking through the lang files. The way you suggested works fine until you pass a few hundred entrys. – Traveller Nov 17 '15 at 19:57
  • @Traveller Long contents are not suppose to be written by the programmer! give them to the site admin. let them set it in their panel. you don't set a long html content in translation string. – Hossein Shahdoost Nov 18 '15 at 05:08
  • @Traveller As long as you generate the translation file that wouldn't be a problem. Translation generator takes care of everything. – Hossein Shahdoost Nov 18 '15 at 05:10
  • Course the programmer doesn't write the long content but there is still the problem of having the lang structured so you or someone else doesn't have to look and check the lang every time he wants to use something – Traveller Nov 18 '15 at 13:12
  • @Traveller Why do we have to check every time we want to write something? – Hossein Shahdoost Nov 18 '15 at 14:23
  • Because how can the programmer know if the adequate "username and password do not match" message is placed under app or under auth or under something else? What if the message goes "THE username and password do not match"? How would he know about that THE prefix? What if you have 3 or 4 types of similar messages that have to be different depending on the context? Example: message appears in an alert, message is displayed inside the for (on the right of the concerning input field), message is sent to and admin as a report... – Traveller Nov 23 '15 at 15:38
  • Try to see this question. It has the naming problem better explained. Maybe try to see this question (http://stackoverflow.com/questions/31785471/which-are-the-suggested-key-naming-strategies-for-trans-in-laravel/33650126#33650126). – Traveller Nov 23 '15 at 15:38
  • @Traveller that's why I said write the full message as the key. That way you don't ever have to check for the message duplication. if you have two messages with the exact same key, the translation would be exactly the same. I've tried many other ways, and I have to say this is the simplest way to manage your messages. And one more thing, you generate your message files don't you? – Hossein Shahdoost Nov 24 '15 at 09:26
  • Yes, of course I generate them. The problem with naming things exactly as the content is. What to do when something changes and you want the change to propagate to everything? You would then either have something that has a key that doesn't equate to the content or you would have to find all the places that use that and change the key everywhere – Traveller Nov 25 '15 at 07:26
  • @Traveller well, each choice has it's downsides. but it's not that difficult when you want to change a word, you change it from the key. and then effect the change in the translation file – Hossein Shahdoost Nov 25 '15 at 08:12
0

I would go with option #4, so you'd have something like this:

/lang/
    /en 
      messages.php
      words.php
    /fr
      message.php
      words.php
    /de
      messages.php
      words.php

This does a few things:

  • It segments out everything very clearly. You know which language to find where. And you know what's in the file associated with the language.
  • The above makes maintenance easier in the future because you can find stuff.
  • It gives you files, by language, that can be translated separately.
  • It puts all the message in one clearly defined place.

One thing to note, is that if your app gets REALLY big and REALLY international, you may want to use ISO language codes instead. For example, european Portugese (pt_PT) and Brazilian Portugese are different and with a global audience you'd probably want to cover both.

Alan Delimon
  • 817
  • 7
  • 14
  • Yes, but that is not the question. The lang is structured in a way that languages are in separate folders that have the same sub-structure and that is done by default. The question is: How to structure things inside the specific language (en, fr, de)? The #4 option has the problem of having 2 huge files that can in the long run (or big applications) be impossible to search through. – Traveller Nov 25 '15 at 07:19