Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for locales (aka culture) #170

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ results/
junit.xml

__pycache__/

.DS_Store
5 changes: 4 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ install:
# install modules
- yarn install
- yarn global add yalc
- yarn add full-icu -W
- set NODE_ICU_DATA=C:\projects\js-lingui\node_modules\full-icu
- setx NODE_ICU_DATA "C:\projects\js-lingui\node_modules\full-icu"

# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- yarn --version
# run tests
- node scripts/test.js
- node --icu-data-dir=C:\projects\js-lingui\node_modules\full-icu scripts/test.js

# Don't actually build.
build: off
Expand Down
6 changes: 5 additions & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
- restore_cache:
key: node-modules-{{ checksum "yarn.lock" }}

- run:
name: Install full-icu
command: yarn add full-icu -W

- run:
name: Install dependencies
command: yarn install
Expand All @@ -39,7 +43,7 @@ jobs:
paths:
- node_modules

- run: node scripts/test.js
- run: env NODE_ICU_DATA=/home/circleci/project/node_modules/full-icu node scripts/test.js

- run:
name: Send coverage report
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -127,6 +128,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -186,6 +188,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -250,6 +253,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -328,6 +332,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -412,6 +417,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -487,6 +493,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -567,6 +574,7 @@ exports[`Children should render 1`] = `
loadLanguageData: [Function],
},
_language: cs,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ exports[`Formats should render 1`] = `
loadLanguageData: [Function],
},
_language: en,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down Expand Up @@ -97,6 +98,7 @@ exports[`Formats should render 1`] = `
loadLanguageData: [Function],
},
_language: en,
_locales: undefined,
plural: [Function],
select: [Function],
selectOrdinal: [Function],
Expand Down
26 changes: 19 additions & 7 deletions packages/core/src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@
import { date, number } from "./formats"
import { isString, isFunction } from "./essentials"

const defaultFormats = (language, languageData = {}, formats = {}) => {
type IntlType = {|
DateTimeFormat: Function,
NumberFormat: Function
|}

declare var Intl: IntlType

const defaultFormats = (language, locales, languageData = {}, formats = {}) => {
locales = locales || language
const { plurals } = languageData
const style = format =>
isString(format) ? formats[format] || { style: format } : format

const replaceOctothorpe = (value, message) => {
return ctx => {
const msg = isFunction(message) ? message(ctx) : message
const norm = Array.isArray(msg) ? msg : [msg]
return norm.map(m => (isString(m) ? m.replace("#", value) : m))
const formatter = new Intl.NumberFormat(locales)
const valueStr = formatter.format(value)
return norm.map(m => (isString(m) ? m.replace("#", valueStr) : m))
}
}

Expand All @@ -28,9 +37,9 @@ const defaultFormats = (language, languageData = {}, formats = {}) => {

select: (value, rules) => rules[value] || rules.other,

number: (value, format) => number(language, style(format))(value),
number: (value, format) => number(locales, style(format))(value),

date: (value, format) => date(language, style(format))(value),
date: (value, format) => date(locales, style(format))(value),

undefined: value => value
}
Expand All @@ -42,13 +51,14 @@ const defaultFormats = (language, languageData = {}, formats = {}) => {
* argument type.
*
* @param language - Language of message
* @param locales - Locales to be used when formatting the numbers or dates
* @param values - Parameters for variable interpolation
* @param languageData - Language data (e.g: plurals)
* @param formats - Custom format styles
* @returns {function(string, string, any)}
*/
function context({ language, values, formats, languageData }: Object) {
const formatters = defaultFormats(language, languageData, formats)
function context({ language, locales, values, formats, languageData }: Object) {
const formatters = defaultFormats(language, locales, languageData, formats)

const ctx = (name: string, type: string, format: any) => {
const value = values[name]
Expand All @@ -63,12 +73,14 @@ function context({ language, values, formats, languageData }: Object) {
export function interpolate(
translation: Function,
language: string,
locales: ?string | string[],
languageData: Object
) {
return (values: Object, formats?: Object = {}) => {
const message = translation(
context({
language,
locales,
languageData,
formats,
values
Expand Down
72 changes: 52 additions & 20 deletions packages/core/src/dev/compile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ describe("compile", function() {
}
}

const prepare = translation =>
interpolate(compile(translation), "en", englishPlurals)
const prepare = (translation, language, locales) =>
interpolate(compile(translation), language || "en", locales, englishPlurals)

it("should compile static message", function() {
const cache = compile("Static message")
Expand Down Expand Up @@ -60,27 +60,59 @@ describe("compile", function() {
expect(cache({ value: "n/a" })).toEqual("They")
})

it("should compile custom format", function() {
const number = prepare("{value, number}")
expect(number({ value: 0.1 })).toEqual("0.1")
const testVector = [
["en", null, "0.1", "10%", "20%", "3/4/2017", "€0.10", "€1.00"],
["fr", null, "0,1", "10 %", "20 %", "04/03/2017", "0,10 €", "1,00 €"],
["fr", "fr-CH", "0,1", "10%", "20%", "04.03.2017", "0.10 €", "1.00 €"]
]
testVector.forEach(tc => {
let language,
locales,
expectedNumber,
expectedPercent1,
expectedPercent2,
expectedDate,
expectedCurrency1,
expectedCurrency2
;[
language,
locales,
expectedNumber,
expectedPercent1,
expectedPercent2,
expectedDate,
expectedCurrency1,
expectedCurrency2
] = tc

const percent = prepare("{value, number, percent}")
expect(percent({ value: 0.1 })).toEqual("10%")
expect(percent({ value: 0.2 })).toEqual("20%")
it(
"should compile custom format for language=" +
language +
" and locales=" +
locales,
function() {
const number = prepare("{value, number}", language, locales)
expect(number({ value: 0.1 })).toEqual(expectedNumber)

const now = new Date("3/4/2017")
const date = prepare("{value, date}")
expect(date({ value: now })).toEqual("3/4/2017")
const percent = prepare("{value, number, percent}", language, locales)
expect(percent({ value: 0.1 })).toEqual(expectedPercent1)
expect(percent({ value: 0.2 })).toEqual(expectedPercent2)

const formats = {
currency: {
style: "currency",
currency: "EUR",
minimumFractionDigits: 2
const now = new Date("3/4/2017")
const date = prepare("{value, date}", language, locales)
expect(date({ value: now })).toEqual(expectedDate)

const formats = {
currency: {
style: "currency",
currency: "EUR",
minimumFractionDigits: 2
}
}
const currency = prepare("{value, number, currency}", language, locales)
expect(currency({ value: 0.1 }, formats)).toEqual(expectedCurrency1)
expect(currency({ value: 1 }, formats)).toEqual(expectedCurrency2)
}
}
const currency = prepare("{value, number, currency}")
expect(currency({ value: 0.1 }, formats)).toEqual("€0.10")
expect(currency({ value: 1 }, formats)).toEqual("€1.00")
)
})
})
8 changes: 4 additions & 4 deletions packages/core/src/formats.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ type IntlType = {|
declare var Intl: IntlType

export function date(
language: string,
locales?: ?string | string[],
format?: DateFormat = {}
): (value: string) => string {
const formatter = new Intl.DateTimeFormat(language, format)
const formatter = new Intl.DateTimeFormat(locales, format)
return value => formatter.format(value)
}

export function number(
language: string,
locales?: ?string | string[],
format?: NumberFormat = {}
): (value: number) => string {
const formatter = new Intl.NumberFormat(language, format)
const formatter = new Intl.NumberFormat(locales, format)
return value => formatter.format(value)
}
24 changes: 17 additions & 7 deletions packages/core/src/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Catalogs = { [key: string]: Catalog }

type setupI18nProps = {
language?: string,
locales?: ?string | string[],
catalogs?: Catalogs,
development?: Object
}
Expand All @@ -39,6 +40,7 @@ function getMessages(catalog) {

class I18n {
_language: string
_locales: ?string | string[]

// Message catalogs
_catalogs: Catalogs
Expand Down Expand Up @@ -78,6 +80,10 @@ class I18n {
return this._language
}

get locales(): ?string | string[] {
return this._locales
}

get messages(): Messages {
return this._activeMessages
}
Expand Down Expand Up @@ -140,7 +146,7 @@ class I18n {
this._cacheActiveLanguage()
}

activate(language: string) {
activate(language: string, locales?: ?string | string[]) {
if (!language) return

if (process.env.NODE_ENV !== "production") {
Expand All @@ -150,12 +156,14 @@ class I18n {
}

this._language = language
this._locales = locales
this._cacheActiveLanguage()
}

use(language: string) {
use(language: string, locales: ?string | string[]) {
return setupI18n({
language,
locales: locales,
catalogs: this._catalogs,
development: this._dev
})
Expand All @@ -176,10 +184,12 @@ class I18n {
}

if (typeof translation !== "function") return translation
return interpolate(translation, this.language, this.languageData)(
values,
formats
)
return interpolate(
translation,
this.language,
this.locales,
this.languageData
)(values, formats)
}

pluralForm(
Expand All @@ -199,7 +209,7 @@ function setupI18n(params?: setupI18nProps = {}): I18n {
}

if (params.catalogs) i18n.load(params.catalogs)
if (params.language) i18n.activate(params.language)
if (params.language) i18n.activate(params.language, params.locales)

return i18n
}
Expand Down
Loading