Skip to content

Latest commit

 

History

History
498 lines (387 loc) · 9.57 KB

File metadata and controls

498 lines (387 loc) · 9.57 KB
id title
linter
eslint-plugin-formatjs

This eslint plugin allows you to enforce certain rules in your ICU message.

Usage

import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

<Tabs groupId="npm" defaultValue="npm" values={[ {label: 'npm', value: 'npm'}, {label: 'yarn', value: 'yarn'}, ]}>

npm i -D eslint-plugin-formatjs
yarn add -D eslint-plugin-formatjs

Then in your eslint config:

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/no-offset": "error"
  }
}

React

Currently this uses intl.formatMessage, defineMessage, defineMessages, <FormattedMessage> from react-intl as hooks to verify the message. Therefore, in your code use 1 of the following mechanisms:

import {defineMessages, defineMessage} from 'react-intl'

const messages = defineMessages({
  foo: {
    defaultMessage: 'foo',
    description: 'bar',
  },
})

defineMessage({
  defaultMessage: 'single message',
})
import {FormattedMessage} from 'react-intl'
;<FormattedMessage defaultMessage="foo" description="bar" />
function foo() {
  intl.formatMessage({
    defaultMessage: 'foo',
  })
}

Vue

This will check against intl.formatMessage, $formatMessage function calls in both your JS/TS & your SFC .vue files. For example:

<template>
  <p>
    {{
      $formatMessage({
        defaultMessage: 'today is {now, date}',
      })
    }}
  </p>
</template>

Available Rules

blacklist-elements

This blacklists usage of specific elements in ICU message.

Why

  • Certain translation vendors cannot handle things like selectordinal

Available elements

enum Element {
  // literal text, like `defaultMessage: 'some text'`
  literal = 'literal',
  // placeholder, like `defaultMessage: '{placeholder} var'`
  argument = 'argument',
  // number, like `defaultMessage: '{placeholder, number} var'`
  number = 'number',
  // date, like `defaultMessage: '{placeholder, date} var'`
  date = 'date',
  // time, like `defaultMessage: '{placeholder, time} var'`
  time = 'time',
  // select, like `defaultMessage: '{var, select, foo{one} bar{two}} var'`
  select = 'select',
  // selectordinal, like `defaultMessage: '{var, selectordinal, one{one} other{two}} var'`
  selectordinal = 'selectordinal',
  // plural, like `defaultMessage: '{var, plural, one{one} other{two}} var'`
  plural = 'plural',
}

Example

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/blacklist-elements": [2, ["selectordinal"]]
  }
}

enforce-description

This enforces description in the message descriptor.

Why

  • Description provides helpful context for translators
import {defineMessages} from 'react-intl'

const messages = defineMessages({
  // WORKS
  foo: {
    defaultMessage: 'foo',
    description: 'bar',
  },
  // FAILS
  bar: {
    defaultMessage: 'bar',
  },
})

Options

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/enforce-description": ["error", "literal"]
  }
}

Setting literal forces description to always be a string literal instead of function calls or variables. This is helpful for extraction tools that expects description to always be a literal

enforce-default-message

This enforces defaultMessage in the message descriptor.

Why

  • Can be useful in case we want to extract messages for translations from source code. This way can make sure people won't forget about defaultMessage
import {defineMessages} from 'react-intl'

const messages = defineMessages({
  // WORKS
  foo: {
    defaultMessage: 'This is default message',
    description: 'bar',
  },
  // FAILS
  bar: {
    description: 'bar',
  },
})

Options

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/enforce-default-message": ["error", "literal"]
  }
}

Setting literal forces defaultMessage to always be a string literal instead of function calls or variables. This is helpful for extraction tools that expects defaultMessage to always be a literal

enforce-placeholders

Makes sure all values are passed in if message has placeholders (number/date/time/plural/select/selectordinal). This requires values to be passed in as literal object (not a variable).

// WORKS, no error
<FormattedMessage
  defaultMessage="this is a {placeholder}"
  values={{placeholder: 'dog'}}
/>

// WORKS, no error
intl.formatMessage({
  defaultMessage: 'this is a {placeholder}'
}, {placeholder: 'dog'})

// WORKS, error bc no values were provided
<FormattedMessage
  defaultMessage="this is a {placeholder}"
/>

// WORKS, error bc no values were provided
intl.formatMessage({
  defaultMessage: 'this is a {placeholder}'
})

// WORKS, error bc `placeholder` is not passed in
<FormattedMessage
  defaultMessage="this is a {placeholder}"
  values={{foo: 1}}
/>

// WORKS, error bc `placeholder` is not passed in
intl.formatMessage({
  defaultMessage: 'this is a {placeholder}'
}, {foo: 1})

// DOESN'T WORK
<FormattedMessage
  defaultMessage="this is a {placeholder}"
  values={someVar}
/>

// DOESN'T WORK
intl.formatMessage({
  defaultMessage: 'this is a {placeholder}'
}, values)

Options

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/enforce-placeholders": [
      "error",
      {
        "ignoreList": ["foo"]
      }
    ]
  }
}
  • ignoreList: List of placeholder names to ignore. This works with defaultRichTextElements in react-intl so we don't provide false positive for ambient global tag formatting

enforce-plural-rules

Enforce certain plural rules to always be specified/forbidden in a message.

Why

  • It is recommended to always specify other as fallback in the message.
  • Some translation vendors only accept certain rules.

Available rules

enum LDML {
  zero = 'zero',
  one = 'one',
  two = 'two',
  few = 'few',
  many = 'many',
  other = 'other',
}

Example

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/enforce-plural-rules": [
      2,
      {
        "one": true,
        "other": true,
        "zero": false
      }
    ]
  }
}

no-camel-case

This make sure placeholders are not camel-case.

Why

  • This is to prevent case-sensitivity issue in certain translation vendors.
import {defineMessages} from 'react-intl'

const messages = defineMessages({
  // WORKS
  foo: {
    defaultMessage: 'foo {snake_case} {nothing}',
  },
  // FAILS
  bar: {
    defaultMessage: 'foo {camelCase}',
  },
})

no-emoji

This prevents usage of emoji in message.

Why

  • Certain translation vendors cannot handle emojis.
  • Cross-platform encoding for emojis are faulty.
import {defineMessages} from 'react-intl'

const messages = defineMessages({
  // WORKS
  foo: {
    defaultMessage: 'Smileys & People',
  },
  // FAILS
  bar: {
    defaultMessage: '😃 Smileys & People',
  },
})

no-multiple-whitespaces

This prevents usage of multiple consecutive whitespaces in message.

Why

  • Consecutive whitespaces are handled differently in different locales.
  • Prevents \ linebreaks in JS string which results in awkward whitespaces.
import {defineMessages} from 'react-intl'

const messages = defineMessages({
  // WORKS
  foo: {
    defaultMessage: 'Smileys & People',
  },
  // FAILS
  bar: {
    defaultMessage: 'Smileys &   People',
  },
  // FAILS
  baz: {
    defaultMessage: 'this message is too long \
    so I wanna line break it.',
  },
})

no-multiple-plurals

This prevents specifying multiple plurals in your message.

Why

  • Nested plurals are hard to translate across languages so some translation vendors don't allow it.
import {defineMessages} from 'react-intl'

const messages = defineMessages({
    // WORKS
    foo: {
        defaultMessage: '{p1, plural, one{one}}',
    },
    // FAILS
    bar: {
        defaultMessage: '{p1, plural, one{one}} {p2, plural, one{two}}',
    }
    // ALSO FAILS
    bar2: {
        defaultMessage: '{p1, plural, one{{p2, plural, one{two}}}}',
    }
})

no-offset

This prevents specifying offset in plural rules in your message.

Why

  • Offset has complicated logic implication so some translation vendors don't allow it.
import {defineMessages} from 'react-intl'

const messages = defineMessages({
  // PASS
  foo: {
    defaultMessage: '{var, plural, one{one} other{other}}',
  },
  // FAILS
  bar: {
    defaultMessage: '{var, plural, offset:1 one{one} other{other}}',
  },
})

enforce-id

This enforces generated ID to be set in MessageDescriptor.

Why

Pipelines can enforce automatic/manual ID generation at the linter level (autofix to insert autogen ID) so this guarantees that.

import {defineMessages} from 'react-intl';

const messages = defineMessages({
  // PASS
  foo: {
    id: '19shaf'
    defaultMessage: '{var, plural, one{one} other{other}}',
  },
  // FAILS
  bar: {
    id: 'something',
    defaultMessage: '{var, plural, offset:1 one{one} other{other}}',
  },
  // FAILS
  bar: {
    defaultMessage: '{var, plural, offset:1 one{one} other{other}}',
  },
});

Options

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/enforce-id": [
      "error",
      {
        "idInterpolationPattern": "[sha512:contenthash:base64:6]"
      }
    ]
  }
}
  • idInterpolationPattern: Pattern to verify ID against

no-id

This bans explicit ID in MessageDescriptor.

Why

We generally encourage automatic ID generation due to these reasons. This makes sure no explicit IDs are set.