TIP: Use Markdown or, <pre> for multi line code blocks / <code> for inline code.
Forums are in read-only mode while we transition to new software.
I18n module for Kohana 3
  • Introduction


    This module started as a helper class to achieve accurate language-dependent plural inflections, but has grown into almost complete alternative to Kohana 3 I18n system.


    Current features are:


    • Support for multiple translation options for any term
    • Support for deep array structures in i18n files
    • Choosing correct translation option when translating plural amount of any term, based on
      CLDR Language Plural Rules
    • Translating and correctly inflecting time spans

    Why would you want to use this


    • You want to be able to use ___('user.register.complete'), like in old Kohana 2.3.4 days
    • You also want to use just ___('User password') for short strings
    • You want to inflect the translations depending on various circumstances, such as user's gender and so on.
    • You want to translate things like 'I've scanned X directories and found Y files' accurately to any language.
    • You have some legacy code using original Kohana I18n system and you don't want it to break, and you want to reuse some of
      its translations at the same time.
    • You want to have better Date::fuzzy_span() output, with actual numbers, again, in any language.
    • You want your validation error messages to be grammatically accurate, too.

    The ___() function


    The ___() function (3 underscores, as opposed to 2 underscores being standard Kohana translation function) does the same thing as its original prototype does, it translates stuff. It has 2 differencies though:


    1. It won't skip translation, if source and destination languages are the same. I.e. if your client wants you to change 'sign in'
      in your application to 'log in', you can do so in the i18n file for default language and don't have to care about all the places
      in your source code, that call ___('sign in').
    2. It accepts 2nd optional string or numeric parameter, for providing translation context.

    For those, who like shorthands, this is a good news, now you can have whatever keys in your i18n files you want. You can have them
    all-string ('user.register.complete' => 'The user has registered successfully') or structured as Kohana messages, which looks cleaner.


    More info


    For more information and usage, see readme @ github:


    http://github.com/czukowski/I18n_Plural#readme


    It's formally tested, but might want some improvements. Anyone who cares, can try an see how it works in their applications. Feedbacks welcome.

  • I have some problem with date formatting.

    in my translation file (i18n/pl.php) is:

    ...
    'date.minute_ago' => array(
    'one' => 'minutę temu',
    'few' => '{delta} minuty temu',
    'other' => '{delta} minut temu',
    ),
    ...


    And function I18n_Date::fuzzy_span()

    ...
    echo Kohana::Debug( I18n_Date::fuzzy_span(time() - 180) ); // 3:00 ago
    ...

    return: string(13) "3 minut temu"

    But when i manually run:

    ...
    echo Kohana::Debug( ___('date.minute_ago', 3, array('{delta}' => 3)) );
    ...

    return: string(13) "3 minuty temu"

    The same problem is with hours etc.


    P.S. My i18n::lang() return "pl-pl"

    P.S.2 I think that better use ":delta" than "{delta}", as is generally used in Kohana
  • Problem is in line 75 in file date.php

    $delta = round($delta);


    $delta must be a integer, not float.

    $delta = (int) round($delta);
  • Thanks for the input, I'll update the function.

    p.s.: for the date/time part I aimed to provide consistent results with MooTools' Date functions. I've chosen to use '{delta}' so that if I wanted to change these strings, I could output them easier as Javascript and reuse them, because of the way how MooTools uses String.substitute() method.

    Edit: issue fixed.
  • I've also found, that month and day names were translated when using RFC2822, and implemented a workaround.
  • I've pushed an update, that enables use of structured translations, such as:

    return array(
    'login' => array(
    'logout' => array(
    'back' => 'Click here to log back in',
    'label' => 'You have been logged out',
    ),
    'reset' => array(
    'back' => 'Back to login',
    'email' => 'Your email',
    'fail' => 'The email address you\'ve entered is not registered here',
    'legend' => 'To reset password, enter your email address',
    'label' => 'Reset password',
    ),
    ),
    ...
    );

    Usage:

    echo ___('login.logout.label');
    Note, that it'll work either way, whether you have it as above, or this:

    return array(
    'login.logout.label' => 'You have been logged out',
    ...
    );

    To enable that, you need to connect the file, that overrides default methods from Kohana_I18n (put 'i18n.php' with the following contents to your 'application' folder):

    class I18n extends I18n_Core {}
  • I've pushed an update, that allows for correct inflection of validation messages, for example, 'Field must be exactly one character long' or 'Field must be exactly 10 characters long'.

    see readme for details here: https://github.com/czukowski/I18n_Plural

  • I still don't see a good example of something like 'I've scanned X directories and found Y files' could you prove it? Or a link to where you did that? I might have missed it.

  • This looks like a fantastic module and could be a huge lifesaver for the project I'm working on. Thanks for working on this!

    The only thing I saw that I wanted to comment on was the order of parameters with the ___() function. The context stuff is awesome, but it'd be nice if the parameters lined up better with how Kohana does it now with the __() function (string, value, language). I think that the language parameter can still be the last one, but it'd be awesome if it could be (string, value, context, language).

    The reason I suggest that is because I'm porting a massive application from CI over to KO and I'd love to be able to use one function for all the I18n stuff. Since the majority of people using my application have almost zero experience with PHP, it'd be great to be consistent with all the I18n stuff so that when they inevitably do try to do something themselves, there's no confusion about when to use __() and when to use ___().

    Does that make sense or am I just completely off my rocker on this one?

  • @Zeelot3k

    you're right, there's no example of it in readme, but it's no magic really:

    echo ___('I\'ve scanned :where and found :what', array(
        ':where' => ___(':count directories', $x, array(':count' => $x)),
        ':what' => ___(':count files', $y, array(':count' => $y)),
    ));
    

    Sure, not as clean looking as the simplest examples, but IMO still better than some insane syntax with inline rules and whatnot :)

    @agentphoenix

    I agree, that the parameter order is primarily a matter of preference. I've just found it easier to support ___($string, $context, $parameters, $language) than ___($string, $parameters, $context, $language), here's why:

    if the $context parameter is omitted, I can tell by looking at is_array($context), because in this case, the $parameters are the 2nd. If $context is 3rd parameter and $language the last, I can't tell it as easily, which of them is omitted, because they can be the same type. See init.php on how it works exactly.

    So you should be safe to use it the same way as __() function. Any of these should work:

    ___($string, $context, $parameters, $language);
    ___($string, $context, $parameters);
    ___($string, $parameters);
    ___($string, $context);
    

    Just note, that the $language parameter has another meaning, than in original __() function, it is target language instead of source.

  • Intresting module.

    Any specific reason you use

    echo ___('hello.myage', 10, array(':age' => 10));
    

    Theres no way to make :age default to 10 rather then having to define it, or make the amount default to first array value?

  • @copy112

    I was thinking of it before, but concluded that could be confusing, if there was another 'magic' parameter, that's applied automatically. The users would have to remember how it's called and use it.

    I hope it's not very inconvenient to have to apply the same number twice in such cases, but if it is, you can always fork and modify the ___() function with the magic parameter :)

  • @czukowski

    If I can use it just like __() then I'm golden! Thanks for your work on this, you have no idea how much of a lifesaver it is.

  • Why not just use gettext? AFAIK, it has been doing all this very robust translation stuff for years.

  • The application I'm building is a distributed system that will mostly be installed on free shared hosts where gettext might not be available. Add to that the fact that 99% of the users for the system know next to nothing about PHP, I'm in a situation where I need the simplest, most straightforward solution.

    Since the gettext option might not work for upwards of 50% of my customers, it really isn't an option. I looked in to the gettext PHP library, but every benchmark I've seen says it's prohibitively slow. Being able to do i18n in a clean, straightforward way that isn't going to confuse users when they do decide to extend view files is crucial and I think this module is going to help with that. I'd love to use PHP's native solution, but until built-in to PHP and compiled by default, I can't trust that it'll be there.

  • Quite a reasonable response. Carry on, folks!

  • Now I just need to track down why it isn't working in KO 3.1...

  • This is because Kohana 3.1 bootstrap.php calls I18n::lang() before modules are loaded and you have class I18n extends I18n_Core, which cannot be found at that point. You need to move I18n::lang() somewhere after that.

  • Just a quick note, Kohana 3.1 is now supported in it's own branch.

    The I18n::lang() thing in bootstrap.php isn't required anymore, as it's now done automatically in module's init.php

  • Great module!

    One problem. Getting a number unit test errors. Are the fuzzy dates not to match Kohana Date?

    PHPUnit output: http://pastie.org/1765006

    Thank you.

  • No, they don't. Fuzzy span uses this module's functionality to output approximate time measure, that's less fuzzy ('1 minute', '2 minutes' and so on). Same goes for some validation messages ('Test 3 must be exactly one character long', 'Test 3 must be exactly 45 characters long',..)

    I've made a few tests to cover the new behavior, but haven't realized Kohana own tests would fail because of that. Now I'm not sure what would be the best way out.

  • Awesome module! Thank you. Keep it going

  • I just downloaded this and it seems awesome. However, dot-notation for arrays doesn't seem to work.

    If I define my keys like 'some.key' => 'some value', ___('some.key') works fine. If I do the same but as an array it doesn't work and 'some.key'is left untranslated.

    The same thing happens if I try to use the date functions, Date::fuzzy_span($time, $time - 10); returns 'date.less_than_a_minute_ago'.

    Has something changed recently so that I need to do things differently?

  • Do you mean it doesn't work, when you have it like this?

    return array(
        'some' => array(
            'key' => 'some value',
        ),
    );
    

    It uses Arr::path() to get translations, which hasn't changed a long time, AFAIK... What Kohana version do you have?

  • Nevermind, hadn't read down to the installation part of the readme yet so i18n wasn't overridden.

    Now I've overridden the date class too and fuzzy_span works with a few seconds but begins returning NULL if the interval is a minute or more (so, when the translations start being arrays). Not sure what's up that but I'll try to locate the error.

    I don't really need dates so it's not a big deal though. I'm on 3.1/develop.

  • Dope module.

  • @johlin

    I can't really say what could be the problem. There are some tests covering new fuzzy_span function, could you try to enable unittest module and execute them, and maybe even debug to see what's going wrong? It's i18n_plural.date group; all these tests pass on my installation.

  • @czukowski

    Found the problem. I copied i18n/en.php from the module folder to my application folder as I'm used to doing when overriding files from modules. I found out that instead of just loading the first file it finds, this module merges all of them together (which by the way is great) and this process somehow causes fuzzy_span to return null down the line. Probably because array_merge_recursive appends rather than replaces if something is identical.

    So I removed the duplicates from application/i18n/en.php and now everything works fine! :)

  • I found out that instead of just loading the first file it finds, this module merges all of them together (which by the way is great)

    This is Kohana default behavior for i18n, config and messages files. I've just had to override the native method to merge arrays recursively, otherwise they're identical.

    I'm going to see how array_merge_recursive affects duplicate entries, but my understanding of it was, that it should've really merged the arrays, hence the name...

  • I think the arrays were merged but in the wrong way.

    Let's look at the following, for example:
    $array1 = array('some' => 'value'); $array2 = array('some' => 'other_value'); $merged = array_merge_recursive($array1, $array2);

    What you want to get is the following: array('some' => 'other_value')

    But what you get instead is array('some' => array('value', 'other_value'), which I think is what causes this problem. There is some code in the comments of the PHP manual that might be able to solve this, for example http://www.php.net/manual/en/function.array-merge-recursive.php#102379. I will do some testing when I have some time over.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion