HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Slavic conjugation

OsaOsa
edited July 2010 in Localization
Hi
I'm trying to translate Vanilla to polish and found that polish (and probably other Slavic) "activities" translation will be quite difficult, because verbs in the past tense are inflected for gender.

It can be done by changing ActivityHeadline and ProfileHeadline functions in /library/core/class.format.php
Is there other way to implement Slavic conjugation , like adding some functions to definitions.php
Tagged:
«1

Comments

  • I'm having similar problems with the Romanian translation. I eventually gave up and let everything on the Activity page in English.
  • SS ✭✭
    edited July 2010
    @Osa @Tudor
    Plugin Smart Localization will save you.

    update: Oh, sorry. Didnt read carefully. It will not save you. It is for other translate problems.
  • OsaOsa
    edited July 2010
    Made some progress..

    I added 1 line to /vanilla/applications/dashboard/models/class.activitymodel.php

    public function ActivityQuery() {
    $this->SQL
    ->Select('a.*')
    ->Select('t.FullHeadline, t.ProfileHeadline, t.AllowComments, t.ShowIcon, t.RouteCode')
    ->Select('t.Name', '', 'ActivityType')
    ->Select('au.Name', '', 'ActivityName')
    ->Select('au.Gender', '', 'ActivityGender')
    ->Select('au.Photo', '', 'ActivityPhoto')
    ->Select('ru.Name', '', 'RegardingName')
    ->Select('ru.Gender', '', 'RegardingGender')
    ->From('Activity a')
    ->Join('ActivityType t', 'a.ActivityTypeID = t.ActivityTypeID')
    ->Join('User au', 'a.ActivityUserID = au.UserID')
    ->Join('User ru', 'a.RegardingUserID = ru.UserID', 'left');
    }

    This modification will allow to format string depending on gender in /vanilla/library/core/class.format.php in function ActivityHeadline.
    /**
    * The ActivityType table has some special sprintf search/replace values in the
    * FullHeadline and ProfileHeadline fields. The ProfileHeadline field is to be
    * used on this page (the user profile page). The FullHeadline field is to be
    * used on the main activity page. The replacement definitions are as follows:
    * %1 = ActivityName
    * %2 = ActivityName Possessive
    * %3 = RegardingName
    * %4 = RegardingName Possessive
    * %5 = Link to RegardingName's Wall
    * %6 = his/her
    * %7 = he/she
    * %8 = route & routecode
    * %9 = gender sufix
    *
    * @param object $Activity An object representation of the activity being formatted.
    * @param int $ProfileUserID If looking at a user profile, this is the UserID of the profile we are
    * looking at.
    * @return string
    */
    public static function ActivityHeadline($Activity, $ProfileUserID = '', $ViewingUserID = '') {
    if ($ViewingUserID == '') {
    $Session = Gdn::Session();
    $ViewingUserID = $Session->IsValid() ? $Session->UserID : -1;
    }
    if ($ViewingUserID == $Activity->ActivityUserID) {
    $ActivityName = $ActivityNameP = T('You');
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? 'eś' : 'aś');
    } else {
    $ActivityName = $Activity->ActivityName;
    $ActivityNameP = FormatPossessive($ActivityName);
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? '' : 'a');
    }
    if ($ProfileUserID != $Activity->ActivityUserID) {
    // If we're not looking at the activity user's profile, link the name
    $ActivityNameD = urlencode($Activity->ActivityName);
    $ActivityName = Anchor($ActivityName, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    $ActivityNameP = Anchor($ActivityNameP, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? '' : 'a');
    }
    $Gender = T($Activity->ActivityGender == 'm' ? 'his' : 'her');
    $Gender2 = T($Activity->ActivityGender == 'm' ? 'he' : 'she');
    if ($ViewingUserID == $Activity->RegardingUserID || ($Activity->RegardingUserID == '' && $Activity->ActivityUserID == $ViewingUserID)) {
    $Gender = $Gender2 = T('your');
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? 'eś' : 'aś');
    }

    $IsYou = FALSE;
    if ($ViewingUserID == $Activity->RegardingUserID) {
    $IsYou = TRUE;
    $RegardingName = T('you');
    $RegardingNameP = T('your');
    $GenderSuffix = ($Activity->RegardingGender == 'm' ? 'eś' : 'aś');
    } else {
    $RegardingName = $Activity->RegardingName == '' ? T('somebody') : $Activity->RegardingName;
    $RegardingNameP = FormatPossessive($RegardingName);
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? 'eś' : 'aś');
    }
    $RegardingWall = '';

    if ($Activity->ActivityUserID == $Activity->RegardingUserID) {
    // If the activityuser and regardinguser are the same, use the $Gender Ref as the RegardingName
    $RegardingName = $RegardingProfile = $Gender;
    $RegardingNameP = $RegardingProfileP = $Gender;
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? 'eś' : 'aś');
    } else if ($Activity->RegardingUserID > 0 && $ProfileUserID != $Activity->RegardingUserID) {
    // If there is a regarding user and we're not looking at his/her profile, link the name.
    $RegardingNameD = urlencode($Activity->RegardingName);
    if (!$IsYou) {
    $RegardingName = Anchor($RegardingName, '/profile/' . $Activity->RegardingUserID . '/' . $RegardingNameD);
    $RegardingNameP = Anchor($RegardingNameP, '/profile/' . $Activity->RegardingUserID . '/' . $RegardingNameD);
    $GenderSuffix = ($Activity->RegardingGender == 'm' ? '' : 'a');
    }
    $RegardingWall = Anchor(T('wall'), '/profile/activity/' . $Activity->RegardingUserID . '/' . $RegardingNameD . '#Activity_' . $Activity->ActivityID);
    }
    if ($RegardingWall == '')
    $RegardingWall = T('wall');

    if ($Activity->Route == '')
    $Route = T($Activity->RouteCode);
    else
    $Route = Anchor(T($Activity->RouteCode), $Activity->Route);

    /*
    Debug:
    return $ActivityName
    .'/'.$ActivityNameP
    .'/'.$RegardingName
    .'/'.$RegardingNameP
    .'/'.$RegardingWall
    .'/'.$Gender
    .'/'.$Gender2
    .'/'.$Route
    */

    return sprintf($ProfileUserID == $Activity->ActivityUserID || $ProfileUserID == '' ? T($Activity->FullHeadline) : T($Activity->ProfileHeadline), $ActivityName, $ActivityNameP, $RegardingName, $RegardingNameP, $RegardingWall, $Gender, $Gender2, $Route, $GenderSuffix);
    }
    This hard-coded suffixes works for me ;-)
    But now, how to overwrite (overload??) ActivityHeadline function from definitions.php ?
  • @Tim, this is great work by @Osa.

    If it is possible this should be implemented in V2.01, bacause all eastern Europe has this kind of conjunction...

    In our languages we use different verbs he and she.
    He says: / On je rekao:
    She says: / Ona je rekla:

    Nice add on.
  • ToddTodd Vanilla Staff
    @Osa, this is great work. I try and get in in to core in a generic way. I edited your post so I can follow the indentation a bit better. Hope you don't mind.
  • ToddTodd Vanilla Staff
    edited July 2010
    Can you answer a question for me? Is there is a different gender suffix for second person (you, your) and third person (he, his)? Could we just have translations for:

    GenderSuffix.Second.m = 'eś'
    GenderSuffix.Second.f = 'aś'
    GenderSuffix.Third.m = ''
    GenderSuffix.Third.f = 'a'

    Also, I'm going to be putting support for translating the activities in your language files so they will be portable.
  • @Todd Yes, we have different gender suffix for second and third person in past tense and You made translation strings correct.

    And another small mod

    $IsYou = FALSE;
    if ($ViewingUserID == $Activity->RegardingUserID) {
    $IsYou = TRUE;
    $RegardingName = T('you');
    $RegardingNameP = T('your');
    $GenderSuffix = ($Activity->RegardingGender == 'm' ? 'eś' : 'aś');
    } else {
    $RegardingName = $Activity->RegardingName == '' ? T('somebody') : $Activity->RegardingName;
    $RegardingNameP = FormatPossessive($RegardingName);
    if ($Activity->ActivityUserID != $ViewingUserID) {
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? '' : 'a');
    } else {
    $GenderSuffix = ($Activity->ActivityGender == 'm' ? 'eś' : 'aś');
    }

    }
    $RegardingWall = '';
    Feel free to make changes more generic.
  • ToddTodd Vanilla Staff
    I've pushed a change with gender suffix support to unstable. It would be great if you guys could try out the fix before we release 2.0.1 to get any bugs out.

    If you want to try the fix, but don't want to copy everything then the only file affected is /library/core/class.format.php.
  • OsaOsa
    edited July 2010
    Line 113 $GenderSuffixCode = "GenderSuffix.$GenderSuffix.{$Activity->ActivityGender}";
    Should be
    $GenderSuffixCode = "GenderSuffix.$GenderSuffixCode.{$Activity->ActivityGender}"; 
    to get suffix gender like GenderSuffix.Third.m

    Another problem is, that gendersuffix depends on Regarding user gender(if any)!
    ie i get

    # GenderSuffix.Third.f -- my debug

    Misiek utworzyła konto użytkownikowi Michalina.
    Misiek(male) created account for Michalina(female)
    This shoud be
    Misiek utworzył konto użytkownikowi Michalina.

    But
    Michalina dołączyła
    Michalina joined

    And
    Misiek dołączył
    Misiek joined.

    So this is why i added
    ->Select('ru.Gender', '', 'RegardingGender')
    to /vanilla/applications/dashboard/models/class.activitymodel.php
  • It gets more and more complicated and I don't like that.
  • ToddTodd Vanilla Staff
    @Osa, I forgot about the regarding gender thing. I've made the changes as best I could.

    It would really be great if someone here can get their own branch going in github and send us pull-requests. It will go much faster.
  • OsaOsa
    edited August 2010
    @TiGR It gets more complicated ;-)

    I propose rewriting ActivityHeadline to get it clear.. It works for me, but please test it before committing. Especially Eastern and Central Europe translators could help. I tried to get all conditions , but simply could miss something.

  • public static function ActivityHeadline($Activity,$ProfileUserID = '', $ViewingUserID = '') {

    if ($ViewingUserID == '') {
    $Session = Gdn::Session();
    $ViewingUserID = $Session->IsValid() ? $Session->UserID : -1;
    }

    $GenderSuffixCode = 'First';
    $GenderSuffixGender = $Activity->ActivityGender;
    $Gender = T($Activity->ActivityGender == 'm' ? 'his' : 'her');
    $Gender2 = T($Activity->ActivityGender == 'm' ? 'he' : 'she');
    $ActivityName = $Activity->ActivityName;
    //activity user, no regarding user, no route , possible routecode
    if ($Activity->RegardingUserID=='' && $Activity->Route == NULL) {
    if ($ViewingUserID<>$Activity->ActivityUserID) {
    $GenderSuffixCode = 'Third';
    } else {
    $ActivityName = T('You');
    $Gender = T('your');
    }
    if ($ViewingUserID != $Activity->ActivityUserID) {
    // If we're not looking at the activity user's profile, link the name
    $ActivityNameD = urlencode($Activity->ActivityName);
    $ActivityName = Anchor($ActivityName, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    }
    if ($Activity->RouteCode !='') $Route = T($Activity->RouteCode);
    }
    //activity user, regarding user, no route
    if ($Activity->RegardingUserID>0 && $Activity->Route =='') {

    $GenderSuffixGender = $Activity->RegardingGender;
    if ($ViewingUserID == $Activity->RegardingUserID) {
    $RegardingName = $Activity->RegardingName = T('You');
    $Gender = $Gender2 = T('your');
    } else {
    $GenderSuffixCode = 'Third';
    $RegardingName = $Activity->RegardingName == '' ? T('somebody') : $Activity->RegardingName;
    $RegardingNameD = urlencode($Activity->RegardingName);
    $RegardingName = Anchor($RegardingName, '/profile/' . $Activity->RegardingUserID . '/' . $RegardingNameD);
    $RegardingNameP = Anchor($RegardingNameP, '/profile/' . $Activity->RegardingUserID . '/' . $RegardingNameD);
    }
    if ($ViewingUserID == $Activity->ActivityUserID) {
    $ActivityName = T('You');
    } else {
    // If we're not looking at the activity user's profile, link the name
    $ActivityNameD = urlencode($Activity->ActivityName);
    $ActivityName = Anchor($ActivityName, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    $ActivityNameP = Anchor($ActivityNameP, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    }
    $RegardingWall = '';
    $RegardingWall = Anchor(T('wall'), '/profile/activity/' . $Activity->RegardingUserID . '/' . $RegardingNameD . '#Activity_' . $Activity->ActivityID);
    if ($RegardingWall == '') {
    $RegardingWall = T('wall');
    }
    }
    //activity user, no regarding user, route

    if ($Activity->RegardingUserID=='' && $Activity->Route !='') {
    if ($ViewingUserID==$Activity->ActivityUserID) {
    $ActivityName = $ActivityNameP = T('You');
    } else {
    $GenderSuffixCode = 'Third';
    $ActivityNameP = FormatPossessive($ActivityName);
    }
    if ($ProfileUserID != $Activity->ActivityUserID && $ViewingUserID != $Activity->ActivityUserID) {
    // If we're not looking at the activity user's profile, link the name
    $ActivityNameD = urlencode($Activity->ActivityName);
    $ActivityName = Anchor($ActivityName, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    $ActivityNameP = Anchor($ActivityNameP, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    } else {
    $Gender = T('your');
    }
    $Route = Anchor (T($Activity->RouteCode),$Activity->Route );
    }
    //activity user, regarding user, route
    if ($Activity->RegardingUserID>0 && $Activity->Route !='') {
    $Route = Anchor(T($Activity->RouteCode), $Activity->Route);
    $RegardingName = $Activity->RegardingName;
    $RegardingNameP = FormatPossessive($RegardingName);
    $GenderSuffixGender = $Activity->ActivityGender;
    if ($ViewingUserID==$Activity->ActivityUserID) {
    $ActivityName = T('You');
    $RegardingNameP=T('your');
    $ActivityNameP = FormatPossessive($ActivityName);

    } else {
    $GenderSuffixCode = 'Third';
    // If we're not looking at the activity user's profile, link the name
    $ActivityNameD = urlencode($Activity->ActivityName);
    $ActivityName = Anchor($ActivityName, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    $ActivityNameP = Anchor($ActivityNameP, '/profile/' . $Activity->ActivityUserID . '/' . $ActivityNameD);
    }
    if ($ViewingUserID == $Activity->RegardingUserID) { $RegardingNameP=T('your');$RegardingName=T('You');
    } else {
    // If we're not looking at the regarding user's profile, link the name
    $RegardingNameD = urlencode($Activity->RegardingName);
    $RegardingName = Anchor($RegardingName, '/profile/' . $Activity->RegardingUserID . '/' . $RegardingNameD);
    $RegardingNameP = Anchor($RegardingNameP, '/profile/' . $Activity->RegardingUserID . '/' . $RegardingNameD);
    }
    $RegardingWall = '';
    $RegardingWall = Anchor(T('wall'), '/profile/activity/' . $Activity->RegardingUserID . '/' . $RegardingNameD . '#Activity_' . $Activity->ActivityID);
    if ($RegardingWall == '') $RegardingWall = T('wall');
    }

    // Translate the gender suffix.
    $GenderSuffixCode = "GenderSuffix.$GenderSuffixCode.$GenderSuffixGender";
    $GenderSuffix = T($GenderSuffixCode, '');
    if ($GenderSuffix == $GenderSuffixCode)
    $GenderSuffix = ''; // in case translate doesn't support empty strings.

    /*
    Debug:
    return $ActivityName
    .'/'.$ActivityNameP
    .'/'.$RegardingName
    .'/'.$RegardingNameP
    .'/'.$RegardingWall
    .'/'.$Gender
    .'/'.$Gender2
    .'/'.$Route
    .'/'.$GenderSuffix.($GenderSuffixCode)
    */
    return sprintf(($ProfileUserID == $Activity->ActivityUserID || $ProfileUserID == '' ? T($Activity->FullHeadline) : T($Activity->ProfileHeadline)), $ActivityName, $ActivityNameP, $RegardingName, $RegardingNameP, $RegardingWall, $Gender, $Gender2, $Route, $GenderSuffix);
    }
  • Awesome tips, @Osa! Thanks a lot! I'm translating it into Russian and it really makes my life easier.
  • @Osa, could you, please, show your activity definitions?
  • This just does not work.

    Russian suffixes for varius male/female verbs are different. You can't just specify one suffix and be happy with that. For example:

    Joined - зарегистрировался/зарегистрировалась.
    Edited - отредактировал/отредактировала

    With current code it's quite hard to get into it and impossible to do these varying suffixes.

    I think you should create some api for locales, so that locale developers might write their code. For instance:
    function formatActivityString(<all parameters, including activity code, all names, genders and so on>) {

    switch ($ActivityCode) {
    case "Joined":
    return "$ActivityUserName зарегистрировал"
    . ($ActivityUserGender=='m' ? "ся" : "ась")
    . " на форуме."
    case "Some":
    case "Other":
    case "Activities":
    case "WIth":
    case "Common":
    case "Suffixes":
    $codes = array('containting activity lines and codes');
    return sprintf($codes[$ActivityCode], $ActivityUserName, $ActitvyUserGender=='m' ? "" : "а", $somethingElse);

    default:
    return false; // so that we might know that we should return default string;
    }
    }
    Then somewher in the core:
    if (!function_exists('formatActivityString')) {
    // Fallback to currently used code
    }
  • Why not just handle it on the phrase level? Have a convention. Therefore there is no need to learn language rules, which can vary quite a bit.

    grep is your friend.

  • it's also possible to make sex independent phrases, but they sucks.

    Fb adds "użytkownik" before each username in polish. "Użytkownik" means "user" and is sex independent. Reading "użytkownik" in every post makes me angry ;-). They are my friends, not just users.
    I like @TiGR's way of making translators responsible for quality of translation. AFAIK we have about 20 activities and only some of them are problematic.


  • x00x00 MVP
    edited October 2011
    There is no need to make neutral phrases just have a convention for the locale, like an array lookup. The problem is you are having language syntax issues, that is becuase you are working on the word level rather than phrase level. Phrase level there needn't be syntax issues, there are actually only a limited amount of scenarios. Why should vanilla need natural language processing capabilities? It should be fairly language agnostic. All you need in the convention is gender capability, and the various versions of that saying. Once you start to try an figure out which words go where and when then you need the processing capabilities of google, if you wish to please everyone. Not necessary.

    TiGRs idea is messy IMO. Function overrides are not the best way to extend. I think you should only be extending local conventions, as they apply to output.

    grep is your friend.

  • Just to put some weight on this topic: the German speaking users will also be interested in a solution for this gender-related translations issue :)
Sign In or Register to comment.