Structuring Website Translation files
Asked Answered
M

3

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.

Malleus answered 5/11, 2015 at 16:6 Comment(6)
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 or lcfirst where needed, or as complex as it needs to be, because again there is no general solution.Neu
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.Malleus
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, 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 here on Stack Overflow.Neu
Yes, but there still don't exist almost any examples for larger use-casesMalleus
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 arrayCalvados
Thank you, I am aware of that and am using it. I left it this way just because most examples use this format.Malleus
L
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.

Lexicographer answered 23/11, 2015 at 16:36 Comment(9)
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).Malleus
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.Lexicographer
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.Malleus
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)Malleus
@Malleus 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.Lexicographer
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 accountMalleus
@Malleus 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.Lexicographer
One more question that is also important. How would you structure things inside the specific files?Malleus
@Malleus Key–value pairs. No multi-level messages, as that just confuses things.Lexicographer
C
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.

Crimmer answered 17/11, 2015 at 18:57 Comment(10)
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.Malleus
@Malleus 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.Crimmer
@Malleus As long as you generate the translation file that wouldn't be a problem. Translation generator takes care of everything.Crimmer
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 somethingMalleus
@Malleus Why do we have to check every time we want to write something?Crimmer
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...Malleus
Try to see this question. It has the naming problem better explained. Maybe try to see this question (#31785971).Malleus
@Malleus 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?Crimmer
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 everywhereMalleus
@Malleus 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 fileCrimmer
D
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.

Dali answered 23/11, 2015 at 16:25 Comment(1)
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.Malleus

© 2022 - 2024 — McMap. All rights reserved.