[i18n] How to use plural form?

Please post bug reports, feature requests, or any question regarding the DELPHI AREA projects here.

[i18n] How to use plural form?

Postby arhangelsoft » April 28th, 2013, 9:42 am

Hi, Kambiz!
Can you explain how to use plural form on same word?

Where add it? How to translate, how to understand plural expressions syntax?

Thanks!
arhangelsoft
Junior Member
Junior Member
 
Posts: 39
Joined: February 16th, 2012, 9:56 pm
Location: Belarus, Minsk

Re: [i18n] How to use plural form?

Postby Kambiz » April 28th, 2013, 5:22 pm

Let's have an example:

Suppose we have a simple program written for English audiences that shows the number of monitors connected to the computer.
Code: Select all
if Screen.MonitorCount = 1 then
  TheLabel.Caption := 'You have one monitor'
else
  TheLabel.Caption := Format('You have %d monitors', [Screen.MonitorCount]);

We are going to change this piece of code, so that it can be localized for any arbitrary culture.

We drop a TLocalizer and a TTranslator component on the form and change the code as follow:
Code: Select all
if Screen.MonitorCount = 1 then
  TheText := Translator.GetText('You have one monitor')
else
  TheText := Translator.GetText('You have {0:N0} monitors');
TheLabel.Caption := Localizer.FormatCS(TheText, [Screen.MonitorCount]);

This code looks fine but follows the English language rule for plurals. Depends on the language, plural form of a noun may have one or many forms. In addition to the number of plural forms, the way how plural forms are built differs in each language.

Hopefully, the i18n package knows the number of plural forms for many languages and also knows how to select the right form. We just need to know the rule for the default language of our program (in our case, English).
Code: Select all
TheText := Translator.GetNText(['You have one monitor', 'You have {0:N0} monitors'], Screen.MonitorCount);
TheLabel.Caption := Localizer.FormatCS(TheText, [Screen.MonitorCount]);

As you can see, instead of GetText, we used GetNText method of the Translator object. This method expects an array of strings as the first parameter. The number of strings in this array are identical to the number of plural forms of the programs' default language. The second parameter of this method is the number of items (in our case, monitors) that determines which string should be selected.

Because GetNText uses a custom expression (known as plural forms expression) to select a plural form from the array, there is no constant rule that we can use to find out the arrangement of plural forms in the array. By convention, usually the string at zero index refers to singular form and the rest are plural form variants.

But in fact we need to know which number will select a string in a specific index. For this purpose knowing a bit about the plural forms expression is necessary.

This plural forms expression is a C-like expression, which means:
  • arithmetic, bit-wise, comparison and ternary operators of C are functional
  • a comparison expression evaluates to either 0 (false) or 1 (true)
  • zero evaluates to false and any non-zero value evaluates to true
You can consider the plural forms expression as an inline function with one parameter named n. The parameter n is the number of items in subject. we should
use this value in our expression to evaluate the index of plural forms in the array of strings.

For example, in English language, we use singular form if and only if the quantity is one, otherwise we use plural form (including zero quantity). We decided to put singular form at zero index and plural form at one index. Based on this information, our plural form expression for English language can be:
Code: Select all
(n == 1) ? 0 : 1

We can also take advantage of C-like expression and write a bit more optimized expression:
Code: Select all
n != 1

Both of the above expressions evaluate to zero when n is one and in other cases evaluate to one.

For a language like Persian that has no plural form for nouns, the plural form expression can be as simple as literal 0. But for some languages it would be more complex. As an example, here is the plural form expression for Arabic expression with six plural form variations:
Code: Select all
n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5

In the i18n editor, the plural forms of a text appear on a tabset. The number of tabs is identical to the number of plural forms that the target language has. Besides that, the caption of each tab indicates the expected result of plural forms expression for selecting that item.

You can see or modify the plural forms rule of a language by selecting Translation => Edit Plural Rule... from the menu of the i18n editor.
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Re: [i18n] How to use plural form?

Postby arhangelsoft » April 30th, 2013, 5:27 am

Oh, thanks! BIG Thanks for this!

I have some problems...
I write code:
Code: Select all
{
 TDM - is TDataModule
I using it for translating string resources.
Here is the method for translating plural forms..
}
function TDM.GetTC(aConstId, aCount: Integer): string; overload;
resourcestring
  rsTimeCount_Week = 'One week';
  rsTimeCount_Weeks = '{0:N0} weeks'; //{0:N0} - What this?

  rsTimeCount_Day = 'One day';
  rsTimeCount_Days = '{0:N0} days';

  rsTimeCount_Hour = 'One hour';
  rsTimeCount_Hours = '{0:N0} hours';

  rsTimeCount_Min = 'One minute';
  rsTimeCount_Mins = '{0:N0} minutes';

  rsTimeCount_Sec = 'One second';
  rsTimeCount_Secs = '{0:N0} seconds';

begin
  Result := '';
  case aConstId of
    TC_TIMECOUNT_WEEK:
      Result := ResTransl.GetNText([rsTimeCount_Week,
        rsTimeCount_Weeks], aCount);

    TC_TIMECOUNT_DAY:
      Result := ResTransl.GetNText([rsTimeCount_Day, rsTimeCount_Days], aCount);

    TC_TIMECOUNT_HOUR:
      Result := ResTransl.GetNText([rsTimeCount_Hour,
        rsTimeCount_Hours], aCount);

    TC_TIMECOUNT_MINUTE:
      Result := ResTransl.GetNText([rsTimeCount_Min, rsTimeCount_Mins], aCount);

    TC_TIMECOUNT_SECOND:
      Result := ResTransl.GetNText([rsTimeCount_Sec, rsTimeCount_Secs], aCount);
  end;
end;

I do all as you says. What wrong?

If aCount = 1, translation works fine.
If aCount > 1, translation also in default English is bad:
Image

Russian language have a 3 plural forms.


What about line breaks in the text? How to save it?
arhangelsoft
Junior Member
Junior Member
 
Posts: 39
Joined: February 16th, 2012, 9:56 pm
Location: Belarus, Minsk

Re: [i18n] How to use plural form?

Postby Kambiz » May 1st, 2013, 6:24 pm

The number that to pass to GetNText is not for formatting. It's just for selecting the right plural form.

You must pass the result string to Localizer's FormatCS method. In the i18n help look for FormatCS and FormatValue functions to find out about the available formatting options.

The line break is done in usual Delphi way.
Code: Select all
multilineText := 'First line'#10#13'Second line';
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Re: [i18n] How to use plural form?

Postby arhangelsoft » May 14th, 2013, 7:48 pm

You must pass the result string to Localizer's FormatCS method. In the i18n help look for FormatCS and FormatValue functions to find out about the available formatting options.

Hm, I don't understand how to relize formatting for Russian:
Editor gives formula for plural forms in Russian(3):
Code: Select all
n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2


For example, numbers:
If number = 1 or 21 or 31 or 41 or ..1 result must be "{0:NO} число"
If number = 2 or 3 or 4 or 22 or 23 or 24 or result must be "{0:NO} числа"
If number = 6 or 7 or 8 or 9..20 or 25..30 result must be "{0:NO} чисел"

Currently,
If number = 1 the result is "1 числа"(must be "{0:NO} число"), what is wrong?
I use following code:
Code: Select all
function TDM.GetTC(aConstId, aCount: Integer): string;
resourcestring
  rsTimeCount_Day = 'One day';
  rsTimeCount_Days = '{0:N0} days';
var
  s: string;

begin
  Result := '';
  case aConstId of
    TC_TIMECOUNT_DAY:
      begin
        s := Translator.GetNText([rsTimeCount_Day, rsTimeCount_Days], aCount);
        Result := Localizer.FormatCS(s, [aCount]);
      end;


Can you give more info about this strings and formatting(Any links, differents between your and C# formating)? I don't quite understand how works i18nUtils.FormatCS. I don't know the C# language.
arhangelsoft
Junior Member
Junior Member
 
Posts: 39
Joined: February 16th, 2012, 9:56 pm
Location: Belarus, Minsk

Re: [i18n] How to use plural form?

Postby Kambiz » May 15th, 2013, 4:17 pm

Please open i18n.chm and search for FormatValue function for detailed information about the format strings.

Your code is correct. You have to provide the proper translation in the editor.

According to your plural formula, the plural index for Russian is:
  • plural=0 if number = 1 or 21 or 31 or 41 or ..1
  • plural=1 if number = 2 or 3 or 4 or 22 or 23 or 24
  • plural=2 if number = 6 or 7 or 8 or 9..20 or 25..30
By the way, the format string for your case is {0:N0} not {0:NO}. There is a zero after N.
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Re: [i18n] How to use plural form?

Postby arhangelsoft » June 7th, 2013, 1:48 pm

Hi, Kambiz!

I've a problems with plurals, again =(

I wrote this code for user-friendly time format:
Code: Select all
function TDM.GetTC(aConstId, aCount: Integer): string;
resourcestring
  rsTimeCount_Week = 'One week';
  rsTimeCount_Weeks = '{0:N0} weeks';

  rsTimeCount_Day = 'One day';
  rsTimeCount_Days = '{0:N0} days';

  rsTimeCount_Hour = 'One hour';
  rsTimeCount_Hours = '{0:N0} hours';

  rsTimeCount_Min = 'One minute';
  rsTimeCount_Mins = '{0:N0} minutes';

  rsTimeCount_Sec = 'One second';
  rsTimeCount_Secs = '{0:N0} seconds';
var
  s: string;

begin
  Result := '';
  case aConstId of
    TC_TIMECOUNT_WEEK:
      begin
        s := Translator.GetNText([rsTimeCount_Week, rsTimeCount_Weeks], aCount);
        Result := Localizer.FormatCS(s, [aCount]);
      end;

    TC_TIMECOUNT_DAY:
      begin
        s := Translator.GetNText([rsTimeCount_Day, rsTimeCount_Days], aCount);
        Result := Localizer.FormatCS(s, [aCount]);
      end;

    TC_TIMECOUNT_HOUR:
      begin
        s := Translator.GetNText([rsTimeCount_Hour, rsTimeCount_Hours], aCount);
        Result := Localizer.FormatCS(s, [aCount]);
      end;

    TC_TIMECOUNT_MINUTE:
      begin
        s := Translator.GetNText([rsTimeCount_Min, rsTimeCount_Mins], aCount);
        Result := Localizer.FormatCS(s, [aCount]);
      end;

    TC_TIMECOUNT_SECOND:
      begin
        s := Translator.GetNText([rsTimeCount_Sec, rsTimeCount_Secs], aCount);
        Result := Localizer.FormatCS(s, [aCount]);
      end;
  end;
end;


I'm correctly use the Translator.GetNText and Localizer.FormatCS methods?

Why Translator.GetNText returns plural=0(e.g. "One hour") if aCount = 17?
For English language this problem doesn't exists, but for Russian language if aCount = 17, function must returns plural=2, but return plural=0. What I'm doing wrong?
arhangelsoft
Junior Member
Junior Member
 
Posts: 39
Joined: February 16th, 2012, 9:56 pm
Location: Belarus, Minsk

Re: [i18n] How to use plural form?

Postby Kambiz » June 9th, 2013, 1:40 pm

Hi,

Your code is correct. There was a bug in evaluation of plural rule expression.
I fixed the bug and you can download the update.
Sorry for the inconvenience.
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Re: [i18n] How to use plural form?

Postby arhangelsoft » June 9th, 2013, 3:29 pm

Hi, thanks for this hotfix. You are the best.
arhangelsoft
Junior Member
Junior Member
 
Posts: 39
Joined: February 16th, 2012, 9:56 pm
Location: Belarus, Minsk


Return to DELPHI AREA Projects

Who is online

Users browsing this forum: No registered users and 8 guests

cron