kazupon / vue-i18n

:globe_with_meridians: Internationalization plugin for Vue.js
https://kazupon.github.io/vue-i18n/
MIT License
7.27k stars 860 forks source link

International pluralization #78

Closed Grawl closed 5 years ago

Grawl commented 7 years ago

In English, pluralization looks simple: one and many. But it's not too simple for Russian language. And there is some other languages.

i18n means Internationalization.

I suggest to use something better for pluralization into $tc()/Vue.tc().

For example, smart-plurals.


Now I using it with Vue like this:

// pluralize.js
import Vue from 'vue'
import smartPlurals from 'smart-plurals'
export default function pluralize(string, amount, lang) {
    string = Vue.t(string, lang).split(' | ')
    return smartPlurals.Plurals.getRule(lang)(amount, string)
}

So I can now write different amount of plural forms for different languages in one format: singular | plural for English or singular | few | plural for Russian for example.

And then just pluralize('key', value, Vue.config.lang) as a function in JS

And {{ $t('key') | pluralize(computedProperty()) }} as a filter in templates:

<!-- module.vue -->
<script>
    import filters from './filters'
    export default {
        filters: filters,
        …
    }
</script>
// filters.js
import Vue from 'vue'
import pluralize from './modules/pluralize'
export default {
    pluralize: function(string, amount) {
        return pluralize(string, amount, Vue.config.lang)
    }
}

Other sources you can see in this repo.

skyrpex commented 7 years ago

The need of a better pluralization is there, but I think this is a bit cumbersome.

Wouldn't it be easier if we just let the translations to use the Laravel's pluralization format? 'apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many',.

IMO, it's simpler and more clear. Also, we could reuse Laravel labels... :clap:

Grawl commented 7 years ago

@skyrpex looks good! I like this format.

And, since Laravel supports Vue out of the box, it's good to be compatible.

skyrpex commented 7 years ago

Maybe we could create some sort of abstraction and let the user to register custom pluralization plugins (just like custom formatters)?

//  The simplest implementation
Vue.config.i18nPluralizer = (string, count) => {
  const parts = string.split('|');
  if (count === 1) {
      return parts[0];
  }

  return parts[1];
};
Grawl commented 7 years ago

@skyrpex I think better to include it into vue-i18n. Pluralization should be simple. smart-plurals do.

skyrpex commented 7 years ago

There would be a default implementation for sure, but having the power to customize it is priceless.

Grawl commented 7 years ago

Why should we customize it? There is a languages, each have it's rules.

skyrpex commented 7 years ago

Letting the "pluralizer" to be customizable would make the users able to choose whatever pluralization method they need. This means one could use Laravel's syntax ('apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many') or something that uses smart-plurals, like:

{
    apples: {
        singular: 'There is an apple',
        few: 'There are {count} apples',
        plural: 'There are so many apples',
    }
}
Grawl commented 7 years ago

Sounds good. I agree.

xpepermint commented 7 years ago

How about http://formatjs.io? Having this as vue component would be awesome!

Grawl commented 7 years ago

@xpepermint looks cool!

skyrpex commented 7 years ago

That library looks awesome, I must admit! It handles formatting and pluralization with a single pass, though. The public vue-i18n API should change, then?

xpepermint commented 7 years ago

@kazupon what do you think about ICU format? Is there a chance that we get this into vue-i18n? That would be super awesome :).

xpepermint commented 7 years ago

A quick and dirty proposal/example - it would be great not to depend on Intl and intl-messageformat (i think I saw a version of vue-intl package which implements this) because you need a polyfill for old browsers.

import * as merge from 'lodash.merge';
import IntlMessageFormat from 'intl-messageformat';

export class I18n {
  locale: string;
  messages: any;
  formats: any;

  /*
  * Class constructor.
  */

  constructor ({
    locale = 'en-US',
    messages = {},
    formats = {}
  }: {
    locale: string,
    messages?: any,
    formats?: any
  }) {
    this.locale = locale;
    this.messages = messages;
    this.formats = merge(IntlMessageFormat.formats, formats);
  }

  formatNumber (value: number, options: any = {}) {
    let {format, ...props} = options;
    props = merge(this.formats.number[format], props);

    return new Intl.NumberFormat(this.locale, props).format(value);
  }

  formatDate (value: number | Date, options: any = {}) {
    let {format, ...props} = options;
    props = merge(this.formats.date[format], props);

    return new Intl.DateTimeFormat(this.locale, props).format(value);
  }

  formatTime (value: number | Date, options: any = {}) {
    let {format, ...props} = options;
    props = merge(this.formats.time[format], props);

    if (Object.keys(props).length === 0) {
      props = this.formats.time.short;
    }

    return new Intl.DateTimeFormat(this.locale, props).format(value);
  }

  formatMessage (message: string, vars: any = {}) {
    return new IntlMessageFormat(message, this.locale, this.formats).format(vars);
  }

}
xpepermint commented 7 years ago

Here is another alternative: http://i18njs.com/#pluralisation

kazupon commented 7 years ago

@xpepermint Thanks for your referennce link! 👍 Looks great to me!!

I think that i want to support translation based on ECMA-402. https://github.com/kazupon/vue-i18n/issues/14

In vue-i18n v5.0 later, I'll plan to support it and full-scrach development.

kazupon commented 7 years ago

ref: vue-intl https://github.com/learningequality/vue-intl

xpepermint commented 7 years ago

Yeah, I tried vue-intl, also their vue2 branch but some methods just don't work and I feel there's a lack of interest for fixing it. I've created vue-translated for now because we have to deploy our platform in a month.

Hurray for the decision about ECMA-402! Five Points from Gryffindor :)!

If I go back to the actual theme of this thread ... I believe that pluralization is something that this package should allow for all languages. This is something that currently prevents us from using this package in our projects. What are your plans about that?

rodneyrehm commented 7 years ago

I see Intl.DateFormat and Intl.NumberFormat have made it into v7, thanks ❤️.

What's holding back (ICU) MessageFormat (e.g. via intl-messageformat) as suggested by @xpepermint? Did you decide to not pursue this furthor in favor of having implementors use Custom Formatting as shown in the custom formatting example?

krukid commented 7 years ago

It's actually fairly straight-forward to implement pluralization and relative dates manually. I personally did exactly that, because my whole app takes up about 60K and the RFC/Intl shenanigan takes up 20K per messageformat and relativeformat + another 60K for Intl polyfill, not to mention whatever integration code you still have to write. If you need something pragmatic, then it makes sense to just bundle up your locales along with pluralization functions (see https://github.com/svenfuchs/rails-i18n/tree/master/lib/rails_i18n/common_pluralizations) and whatever locale-specific helpers you need and dynamically import them on locale switch. Thanks to Webpack that's a breeze. The relative dates feature is just a single common function that builds on top of that. Here's my quick and dirty plugin that takes up just 1.5K gzipped https://gist.github.com/krukid/eb4aea19721ed360d27ec4eea818320f, the rest is loaded by loadLocale() when invoked. You can even wrap this up nicely in a vue-router middleware that will block until non-packaged locale is being pulled.

ilyavaiser commented 6 years ago

Is it already implemented?

outOFFspace commented 6 years ago

Nice implementation has Yii2 framework Yii2 guide

mitar commented 6 years ago

I like the smart plurals approach from @Grawl, I hope this could be added.

outOFFspace commented 6 years ago

Any plans to implement this feature?

Xowap commented 6 years ago

I find it interesting that a translation system would propose pluralization that only works in English. This kind of defeats the purpose of translating... And this ticket has been open for almost two years.

In other words, are you guys motherfucking NUTS? You reached version 7 without noticing this?! Wake the fuck up guys...

Thanks :heart:

skyrpex commented 6 years ago

I didn't dig too much into this, but I think you can already implement your own formatter.

adinvadim commented 6 years ago

@kazupon Any plans to implement this feature?

kazupon commented 6 years ago

In the future, I'll implement vue-i18n with full-scratch. In the timing, I might plan to adopt ICU format.

prakhargahlot commented 6 years ago

We're using vue-i18n too, and as we look at expanding to other platforms and languages, this feature would really help us in implementing without superfluous plugins.

victor-ponamariov commented 6 years ago

So there is still no possibility to work with pluralization in other languages except English?

3amprogrammer commented 5 years ago

Unfortunately below solution doesn't cover this syntax:

'apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many'

It only supports simple plural forms discovered based on getPluralIndex function. I also had to do my own formatting as BaseFormatter isn't exported...

class PluralizableMessageFormatter {
    constructor(locale) {
        this.locale = locale;
    }

    interpolate(message, values) {
        // This is the greatest hack in history
        if (!values || !values.hasOwnProperty('number')) {
            return [message];
        }

        const index = getPluralIndex(this.locale, values.number);
        const template = message.split('|')[index];
        return [
            Object
            .keys(values)
            .reduce((result, key) => result.replace(`{${key}}`, values[key]), template.trim())
        ];
    }
}

You ignore $tc function all together and instead you use $t with number key...

{{ $t('messages.years_old', { number: contractor.age, age: contractor.age }) }}

The number key imitates second argument in normal trans_choice function. You also need to create getPluralIndex.js copied from \Illuminate\Translation\MessageSelector::getPluralIndex. https://gist.github.com/3amprogrammer/56e543472850923936d2b5f2194d6640

Raiondesu commented 5 years ago

@kazupon, would you accept potential PR that implements this feature in an extendable way?

I'd propose simething like an ability to override a function on VueI18n prototype that would behave somewhat similar to fetchChoise.

This would not break any current logic while also allowing users to define custom behaviour for plurals, for example.

Just to clarify if it makes sense for me to do it...

P.S. As an additional argument - this issue is 2 years old and there were multiple somewhat breaking updates in this time period. Maybe this needs to be implemented already?..

Raiondesu commented 5 years ago

Ah, I'll do it anyway...

Raiondesu commented 5 years ago

Everyone in need can install the updated lib from my github for now:

npm install raiondesu/vue-i18n#master