3

I'm running into a problem with the __ function, and I'm not sure whether it's a bug in Cake (3.2.8), Aura\Intl, or my code. I've tried the same thing in Cake 1.3, and it works as I expect it to, but it's possible that my expectations are simply that way because that's how it worked in 1.3. :-)

When I am building my menus, I use things like __('Teams'), but I also have pages that use things like __n('Team', 'Teams', count($player->teams)). The i18n shell extracts these into the default.pot separately, so when I translate it to French, it's like this:

msgid "Teams"
msgstr "Équipe"

msgid "Team"
msgid_plural "Teams"
msgstr[0] "Équipe"
msgstr[1] "Équipes"

If I call __('Team'), I correctly get 'Équipe' returned, and if I call __n('Team', 'Teams', $x), I correctly get 'Équipe' or 'Équipes' returned, depending on the value of $x. But if I call __('Teams') I get back

Array
(
    [0] => Équipe
    [1] => Équipes
)

This is the case even if I eliminate the msgid "Teams" section, leaving only the plural definition.

In Cake 1.3, __('Teams') would simply return 'Équipes'. (Don't know what it might do in 2.x, as I skipped over that entirely.) So, whose bug is this?

Greg Schmidt
  • 5,010
  • 2
  • 14
  • 35
  • `In Cake 1.3, __('Teams') would simply return 'Équipes'.` Assuming that's true, I'm quite surprised - the internal logic for parsing po files has been rewritten several times, but the fundamental mechanism explained in both answers below, hasn't changed. – AD7six May 16 '16 at 13:43

2 Answers2

2

You have two Teams message IDs. The problem is that the CakePHP message file parser stores the messages in a key => value fashion, where the message ID is used as the key, resulting in the Teams messages for msgid_plural to override the Teams message from the preceding msgid.

https://github.com/cakephp/cakephp/blob/3.2.8/src/I18n/Parser/PoFileParser.php#L149 https://github.com/cakephp/cakephp/blob/3.2.8/src/I18n/Parser/PoFileParser.php#L172

https://github.com/cakephp/cakephp/blob/3.2.8/src/I18n/Parser/MoFileParser.php#L137-L140

Since gettext seems to be able to handle this, I'd say that it's at least a missing feature (which might be intentional), however it might even be a bug, I can't tell for sure (but I'd tend towards bug). For clarification, open an issue over at GitHub.

To (temporarily) workaround this issue you could use contexts.

ndm
  • 59,784
  • 9
  • 71
  • 110
2

Duplicate message definition

This is problematic:

msgid "Teams" <- same string
msgstr "Équipe"

msgid "Team"
msgid_plural "Teams" <- same string

The first means you have or are expecting to have __('Teams') in the application code, which expects to return a string.

The second scenario is going to create ambiguous data when the po file is parsed. The class responsible for converting po files to the array format is the PoFileParser, which contains these lines:

$messages[$singular] = $translation; // <- a string
...
$messages[$key] = $plurals; // <- an array

Where $messages is the array used to lookup translations, indexed by the translation key.

So, the reason for the observed behavior is because this code:

__('Teams'); 

is going to look for $messages['Teams']

This code:

__n('Team', 'Teams', 2); 

is going to look for $messages['Teams'][<index>], and the $messages array is going to contain the parsed data from the plural translation only, which overwrote the "singular" version of the string as it was earlier in the file.

The code-level solution is to ensure that all msgid and msgid_plural keys are unique (since they are essentially, the same thing).

Bad translation definition

You may well find at some point in the future that translations like __('Teams') are very problematic, depending on how that loose word is being used, they are an indicator of bad translation definitions.

To give an example, CakePHP used to have translations of this form in baked output:

...
sprintf(__('Invalid %s', true), 'Team');
...

Which was later changed to:

__('Invalid Team', true)

Because the translation of Invalid %s can change depending on what %s is - so can the translation of %s.

Community
  • 1
  • 1
AD7six
  • 63,116
  • 12
  • 91
  • 123
  • I did have the "Invalid %s" type of strings in the previous (1.3) version of my code, but have come to that same realization and replaced them all in the new version, so I don't expect to run into the problems you discuss in the second part. – Greg Schmidt May 16 '16 at 14:56
  • I'd be fine with deleting the stand-alone "Teams" translation entirely and just keeping the plural one, except that if I use that outside of the `__n` function, I get back the array. :-( – Greg Schmidt May 16 '16 at 14:57
  • what is the context (i.e. the code around it) where you are using `__('Teams')` ? Is it a menu item for example? – AD7six May 16 '16 at 15:02
  • Primarily menus, breadcrumbs, that kind of thing. The same sort of situation is going to arise with a number of my models, I expect; the application is 100,000 lines of code so I'm not looking forward to going through all of that to add contexts... – Greg Schmidt May 16 '16 at 15:06
  • All of the translation functions are wrapped in [function exists checks](https://github.com/cakephp/cakephp/blob/master/src/I18n/functions.php#L17). So you _could_ define that function to check if the return value is an array, and if so call `__n` or simply pop off the last value. Though I think doing that will simply postpone the inevitable task of find and replace `__('Teams')` with `__d('menu', 'Teams')` or `__x('menu', 'Teams')`- something laborious but not complex or difficult to do. – AD7six May 17 '16 at 13:14