Dayjs: Localisation capabilities of the RelativeTime plugin are insufficient for fusional languages

Created on 15 Aug 2018  ·  5Comments  ·  Source: iamkun/dayjs

What do you think about re-working the current locale definition of the RelativeTime plugin to support fusional languages without grammatical mistakes?

Moment.js has more complicated locale definitions, but it allows grammatically correct localisation for Slavic languages, for example. Your Russian localisation suffers from the second problem below and my Czech localisation from both, which makes the localised expressions look so wrong, that they cannot be used in real-world applications.

Problem

  1. Localisation expressions for future ("after %s") and past ("before %s") contain the same rest of the expression. However, they contain different prepositions and thus they may need different declension in the rest of the expression.
  2. There is only one localisation expression for multiple seconds ("%d seconds"), minutes ("%d minutes") etc. However, different numerals may need different declension of the following noun.

For example, Slavic languages do not use just one noun form for singlular (one second) and one for plural (many seconds). Usually there are different forms after numerals 1, 2-4 and 5+. Additionally, the noun changes according to to the grammatical case, which is needed in the particular expression. The grammatical case depends on the preposition, for example.

Examples of English, German Russian and Czech expressions

Years - Past

 a year ago    vor einem Jahr   в прошлом году   vloni (před rokem)
 2 years ago   vor 2 Jahren     2 года назад     před 2 roky
 5 years ago   vor 5 Jahren     5 лет назад      před 5 lety

Years - Future

 in a year     in einem Jahr   через год      za rok
 in 2 years    in 2 Jahren     через 2 года   za 2 roky
 in 5 years    in 5 Jahren     через 5 лет    za 5 let

Days - Past

 yesterday    gestern          вчера          včera
(a day ago    vor einem Tag)   день назад     před jedním dnem)
 2 days ago   vor 2 Tagen      2 дни назад    před 2 dny
 5 days ago   vor 5 Tagen      5 дней назад   před 5 dny

Days - Future

 tomorrow    morgen         завтра         zítra
(in a day    in einem Tag   через день     za den)
 in 2 days   in 2 Tagen     через 2 дни    za 2 dny
 in 5 days   in 5 Tagen     через 5 дней   za 5 dní

Seconds - Past

 a second ago              vor einer Sekonde      секунду назад            před sekundou
 2 seconds ago             vor 2 Sekonden         2 секунды назад          před 2 sekundami
 5 seconds ago             vor 5 Sekonden         5 секунд назад           před 5 sekundami
 a couple of seconds ago   vor wenigen Sekonden   несколько секунд назад   před pár (několika) sekundami

Seconds - Future

 in a second              in einer Sekonde      через секунду            za sekundu
 in 2 seconds             in 2 Sekonden         через 2 секунды          za 2 sekundy
 in 5 seconds             in 5 Sekonden         через 5 секунд           za 5 sekund
 in a couple of seconds   in wenigen Sekonden   через несколько секунд   za pár (několik) sekund

Solution

  1. Duplicate all expressions for future and past instead of combining them with the two prepositions.
  2. Introduce the third expression with the numeral for many (5). Use the current expressions for one (1) and few (2-4).
const locale = {
  name: 'cs',
  weekdays: 'neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota'.split('_'),
  months: 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_'),
  ordinal: n => `${n}.`,
  relativeTime: {
    future: {
      s: 'za několik sekund',
      m: 'za minutu',
      mm: 'za %d minuty',
      mmm: 'za %d minut',
      h: 'za hodinu',
      hh: 'za %d hodiny',
      hhh: 'za %d hodin',
      d: 'zítra',
      dd: 'za %d dny',
      ddd: 'za %d dní',
      M: 'za měsíc',
      MM: 'za %d měsíce',
      MMM: 'za %d měsícú',
      y: 'za rok',
      yy: 'za %d roky',
      yyy: 'za %d let'
    },
    past: {
      s: 'před několika sekundami',
      m: 'před minutou',
      mm: 'před %d minutami',
      mmm: 'před %d minutami',
      h: 'před hodinu',
      hh: 'před %d hodinami',
      hhh: 'před %d hodinami',
      d: 'včera',
      dd: 'před %d dny',
      ddd: 'před %d dny',
      M: 'před měsícem',
      MM: 'před %d měsíci',
      MMM: 'před %d měsíci',
      y: 'vloni',
      yy: 'před %d roky',
      yyy: 'před %d lety'
    }
  }
}

Most helpful comment

My original simplification was not enough. Even the common languages were not covered well with the three plural forms and the single rule for them, as @leovp pointed out.

It would be better to go for the universal solution right away instead of trying something simpler as I did originally. For example, by specifying both the plural rule (a number or a function) and the plural forms as an array of strings, which the plural rule is an index to.

  relativeTime: {
    // Slavic (Slovak, Czech), 3 plural forms for 1, 2-4, 5-
    pluralRule: 8,
    duration: {
      s: 'několik sekund',
      m: 'minuta',
      mm: ['%d minuta', '%d minuty', '%d minut'],
      h: 'hodina',
      hh: ['%d hodina', '%d hodiny', '%d hodin'],
      d: 'den',
      dd: ['%d den', '%d dny', '%d dní'],
      M: 'měsíc',
      MM: ['%d měsíc', '%d měsíce', '%d měsícú'],
      y: 'rok',
      yy: ['%d rok', '%d roky', '%d let']
    },
    future: {
      s: 'za několik sekund',
      m: 'za minutu',
      mm: ['za %d minutu', 'za %d minuty', 'za %d minut'],
      h: 'za hodinu',
      hh: ['za %d hodinu', 'za %d hodiny', 'za %d hodin'],
      d: 'zítra',
      dd: ['za %d den', 'za %d dny', 'za %d dní'],
      M: 'za měsíc',
      MM: ['za %d měsíc', 'za %d měsíce', 'za %d měsícú'],
      y: 'za rok',
      yy: ['za %d rok', 'za %d roky', 'za %d let']
    },
    past: {
      s: 'před několika sekundami',
      m: 'před minutou',
      mm: ['před %d minutou', 'před %d minutami', 'před %d minutami'],
      h: 'před hodinou',
      hh: ['před %d hodinou', 'před %d hodinami', 'před %d hodinami'],
      d: 'včera',
      dd: ['před %d dnem', 'před %d dny', 'před %d dny'],
      M: 'před měsícem',
      MM: ['před %d měsícem', 'před %d měsíci', 'před %d měsíci'],
      y: 'vloni',
      yy: ['před %d rokem', 'před %d roky', 'před %d lety']
    }
  }
  // Plural rule #8
  n => n === 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2

I had to to separate the special singular form from the plural forms. It has usually no number and thus the first plural form (for 1, 21, 31, ...) cannot be reused for it.

I updated #304 with this approach.

Eventually, the plural rules should make it out of the relativeTime plugin to the dayjs core utilities. Or may be event out of this module.

All 5 comments

This is also needed for proper Finnish translation. Otherwise either future or past tense can't work correctly and is unacceptable for real use.

E.g.
in 2 hours -> 2 tunnin kuluttua
2 hours ago -> 2 tuntia sitten

@iamkun please prioritize this issue. I do understand that it will probably be a breaking change (new major release), but it's essential in order to move further and ensure the bright future of dayjs.

A lot of localisations are invalid with the format dayjs has currently.

@limonte, I took care not to break the existing language packs in the suggested fix #304. Czech, Slovak, Russian and Ukrainian language packs are updated. The others are loaded and work as they did before the change. They can be gradually upgraded as the community goes on.

I reused the existing patterns "m" and "mm" for the mnemonic tags "one" and "few". I added pattern "mmm" for the "many" tag. Tag "zero" is not needed for relative time values. Tag "two" is rare and tag "other" has no common rules. Tags "one", "few" and "many" hardcoded for 1, 2-4 and 5+ numbers mean a small change and cover many languages. The future step could be implementing the full CLDR rules. See plural rules on CLDR for more information.)

My original simplification was not enough. Even the common languages were not covered well with the three plural forms and the single rule for them, as @leovp pointed out.

It would be better to go for the universal solution right away instead of trying something simpler as I did originally. For example, by specifying both the plural rule (a number or a function) and the plural forms as an array of strings, which the plural rule is an index to.

  relativeTime: {
    // Slavic (Slovak, Czech), 3 plural forms for 1, 2-4, 5-
    pluralRule: 8,
    duration: {
      s: 'několik sekund',
      m: 'minuta',
      mm: ['%d minuta', '%d minuty', '%d minut'],
      h: 'hodina',
      hh: ['%d hodina', '%d hodiny', '%d hodin'],
      d: 'den',
      dd: ['%d den', '%d dny', '%d dní'],
      M: 'měsíc',
      MM: ['%d měsíc', '%d měsíce', '%d měsícú'],
      y: 'rok',
      yy: ['%d rok', '%d roky', '%d let']
    },
    future: {
      s: 'za několik sekund',
      m: 'za minutu',
      mm: ['za %d minutu', 'za %d minuty', 'za %d minut'],
      h: 'za hodinu',
      hh: ['za %d hodinu', 'za %d hodiny', 'za %d hodin'],
      d: 'zítra',
      dd: ['za %d den', 'za %d dny', 'za %d dní'],
      M: 'za měsíc',
      MM: ['za %d měsíc', 'za %d měsíce', 'za %d měsícú'],
      y: 'za rok',
      yy: ['za %d rok', 'za %d roky', 'za %d let']
    },
    past: {
      s: 'před několika sekundami',
      m: 'před minutou',
      mm: ['před %d minutou', 'před %d minutami', 'před %d minutami'],
      h: 'před hodinou',
      hh: ['před %d hodinou', 'před %d hodinami', 'před %d hodinami'],
      d: 'včera',
      dd: ['před %d dnem', 'před %d dny', 'před %d dny'],
      M: 'před měsícem',
      MM: ['před %d měsícem', 'před %d měsíci', 'před %d měsíci'],
      y: 'vloni',
      yy: ['před %d rokem', 'před %d roky', 'před %d lety']
    }
  }
  // Plural rule #8
  n => n === 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2

I had to to separate the special singular form from the plural forms. It has usually no number and thus the first plural form (for 1, 21, 31, ...) cannot be reused for it.

I updated #304 with this approach.

Eventually, the plural rules should make it out of the relativeTime plugin to the dayjs core utilities. Or may be event out of this module.

fixed in #767

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dakshshah96 picture dakshshah96  ·  5Comments

oliv9286 picture oliv9286  ·  4Comments

eugeneoshepkov picture eugeneoshepkov  ·  3Comments

axelg12 picture axelg12  ·  4Comments

Sunrise1970 picture Sunrise1970  ·  4Comments