.Net Localized Relative Time Messages
Hello everyone,
On the current project, the requirements came from the customer that information about past events should be shown to the client in the form “relative to time” (Message of the form: “The event occurred N minute \ minutes \ minutes ago”). We received message templates for different options, as well as appearance requirements, one of which was that messages should be localized.
“What is complicated here?”, We thought and went to Google, in search of a ready-made solution. Unfortunately, Google posed as a partisan, offered several options for ready-made solutions for building this type of message for the English language, but none of them supported localization.
"There is nothing to do, it will have tosupport the Russian auto industryreinvent your bike, ”we decided. And so, what came of it ...
Localization in our application is implemented through resource files. It was decided that all the necessary information for localization will also be stored in them. The main problem was the definition of rules by which it is necessary to take from a resource a particular meaning of the word. In our case, these were the words “Day”, “Hour”, “Minute”, with the corresponding numeral form.
Having talked with Google again, we found a wonderful site on which the rules for constructing single and multiple word forms for most languages are described and standardized. This was the starting point for writing code.
On the site above, various types of word forms are divided into categories, the most popular of them:
- one
- few
- many
- other
For each category of each language, examples are given, as well as rules, membership in a particular group.
For easier understanding, I will give an example for the English language:
In a nutshell, I’ll tell you how we decided to do this:
For each of the languages that we support, a class is created, with a method for calculating the category to which the word belongs, by the number of objects. Possible resource categories of words in the form {ObjectName}. {Category} are added to the resource file of this language. A farbric class is created that returns the implementation of the aforementioned class depending on the language for which it is necessary to calculate the form of the word. A wrapper class is created to localize the word form, one of the methods of which takes as part of the key the resource key of the object ({ObjectName}) and the number of these objects, and returns a string from the corresponding resource file of the required category.
Well, from words to action! It's time to program ...
The first step was to create an enum containing the categories that we need to work with:
The following is the interface for future classes calculating the category:
Factory Class:
Wrapper class:
Category rendering class for the English language:
For English, the following values were added to the resource file:
DateTime.Days.One day
DateTime.Days.Other days
DateTime.Hours.One hour
DateTime.Hours.Other hours
DateTime.Minutes.One minute
DateTime.Minutes.Other minutes
Well, a little usage example:
That's all, thank you for your attention, I hope the article will be useful to someone.
On the current project, the requirements came from the customer that information about past events should be shown to the client in the form “relative to time” (Message of the form: “The event occurred N minute \ minutes \ minutes ago”). We received message templates for different options, as well as appearance requirements, one of which was that messages should be localized.
“What is complicated here?”, We thought and went to Google, in search of a ready-made solution. Unfortunately, Google posed as a partisan, offered several options for ready-made solutions for building this type of message for the English language, but none of them supported localization.
"There is nothing to do, it will have to
Localization in our application is implemented through resource files. It was decided that all the necessary information for localization will also be stored in them. The main problem was the definition of rules by which it is necessary to take from a resource a particular meaning of the word. In our case, these were the words “Day”, “Hour”, “Minute”, with the corresponding numeral form.
Having talked with Google again, we found a wonderful site on which the rules for constructing single and multiple word forms for most languages are described and standardized. This was the starting point for writing code.
On the site above, various types of word forms are divided into categories, the most popular of them:
- one
- few
- many
- other
For each category of each language, examples are given, as well as rules, membership in a particular group.
For easier understanding, I will give an example for the English language:
| Language name | Code | Category | Examples | Rules |
|---|---|---|---|---|
| English | en | one | 1 | one → n is 1; other → everything else |
| other | 0, 2-999; 1.2, 2.07 ... |
In a nutshell, I’ll tell you how we decided to do this:
For each of the languages that we support, a class is created, with a method for calculating the category to which the word belongs, by the number of objects. Possible resource categories of words in the form {ObjectName}. {Category} are added to the resource file of this language. A farbric class is created that returns the implementation of the aforementioned class depending on the language for which it is necessary to calculate the form of the word. A wrapper class is created to localize the word form, one of the methods of which takes as part of the key the resource key of the object ({ObjectName}) and the number of these objects, and returns a string from the corresponding resource file of the required category.
Well, from words to action! It's time to program ...
The first step was to create an enum containing the categories that we need to work with:
public enum PluralCategories {
One,
Few,
Many,
Other
}
The following is the interface for future classes calculating the category:
public interface ITimeFormatter
{
///
/// Returns category for plural form
///
///
///
PluralCategories GetPluralCategoryByCount(int count);
}
Factory Class:
///
/// Default fabric for TimeFormatters
///
public class TimeFormatterFabric
{
///
/// Returns TimeFormatter for corresponding culture
///
///
///
public ITimeFormatter GetTimeFormatter(CultureInfo cultureInfo)
{
ITimeFormatter result;
if (cultureInfo.Name.StartsWith("en", StringComparison.InvariantCultureIgnoreCase))
{
result = new EnglishTimeFormatter();
} else if (cultureInfo.Name.StartsWith("ru", StringComparison.InvariantCultureIgnoreCase))
{
result = new RussianTimeFormatter();
} else
{
result = new EnglishTimeFormatter();
}
return result;
}
}
Wrapper class:
public class TimeFormatter
{
private readonly ILocalizationService _localizationService;
private readonly ITimeFormatter _timeFormatter;
///
/// Defaults constructor.
///
///
public TimeFormatter(ILocalizationService localizationService)
{
_localizationService = localizationService;
_timeFormatter = new TimeFormatterFabric().GetTimeFormatter(localizationService.CurrentUiCulture);
}
///
/// Returns localized string by key and count of items.
///
///
///
///
public string Localize(string rootResourceKey, int count)
{
var resourceKey = string.Format("{0}.{1}", rootResourceKey, _timeFormatter.GetPluralCategoryByCount(count).ToString());
return _localizationService.Localize(resourceKey);
}
}
Category rendering class for the English language:
public class EnglishTimeFormatter : ITimeFormatter
{
///
/// Return plural category by count for English language rules
///
///
///
public PluralCategories GetPluralCategoryByCount(int count)
{
PluralCategories result = PluralCategories.Other;
if (count == 1)
{
result = PluralCategories.One;
}
return result;
}
}
For English, the following values were added to the resource file:
DateTime.Days.One day
DateTime.Days.Other days
DateTime.Hours.One hour
DateTime.Hours.Other hours
DateTime.Minutes.One minute
DateTime.Minutes.Other minutes
Well, a little usage example:
public static string ToTimeLeftString(this DateTime dateTime) {
var result = new StringBuilder();
var _localizationService = DependencyResolver.Current.GetService();
var timeFormatter = new TimeFormatter(_localizationService);
var currentTime = DateTime.UtcNow;
var timeLeft = currentTime - dateTime;
result.AppendFormat("{0} {1} {2} {3} {4}", timeLeft.Days,
timeFormatter.Localize("DateTime.Days", timeLeft.Days),
_localizationService.Localize("Common.Ago"),
_localizationService.Localize("Common.At"), _localizationService.ConverUtcDateTimeToUsersTimeZone(dateTime).ToShortTimeString());
return result.ToString();
}
That's all, thank you for your attention, I hope the article will be useful to someone.