@robotical/martyblocks
Version:
MartyBlocks based on Scratch for Marty the Robot by Robotical
226 lines (207 loc) • 8.41 kB
JavaScript
// @flow
var parse = require('format-message-parse')
var interpret = require('format-message-interpret')
var plurals = require('format-message-interpret/plurals')
var lookupClosestLocale = require('lookup-closest-locale')
var origFormats = require('format-message-formats')
/*::
import type { Types } from 'format-message-interpret'
type Locale = string
type Locales = Locale | Locale[]
type Message = string | {|
id?: string,
default: string,
description?: string
|}
type Translations = { [string]: ?{ [string]: string | Translation } }
type Translation = {
message: string,
format?: (args?: Object) => string,
toParts?: (args?: Object) => any[],
}
type Replacement = ?string | (string, string, locales?: Locales) => ?string
type GenerateId = (string) => string
type MissingTranslation = 'ignore' | 'warning' | 'error'
type FormatObject = { [string]: * }
type Options = {
locale?: Locales,
translations?: ?Translations,
generateId?: GenerateId,
missingReplacement?: Replacement,
missingTranslation?: MissingTranslation,
formats?: {
number?: FormatObject,
date?: FormatObject,
time?: FormatObject
},
types?: Types
}
type Setup = {|
locale: Locales,
translations: Translations,
generateId: GenerateId,
missingReplacement: Replacement,
missingTranslation: MissingTranslation,
formats: {
number: FormatObject,
date: FormatObject,
time: FormatObject
},
types: Types
|}
type FormatMessage = {
(msg: Message, args?: Object, locales?: Locales): string,
rich (msg: Message, args?: Object, locales?: Locales): any[],
setup (opt?: Options): Setup,
number (value: number, style?: string, locales?: Locales): string,
date (value: number | Date, style?: string, locales?: Locales): string,
time (value: number | Date, style?: string, locales?: Locales): string,
select (value: any, options: Object): any,
custom (placeholder: any[], locales: Locales, value: any, args: Object): any,
plural (value: number, offset: any, options: any, locale: any): any,
selectordinal (value: number, offset: any, options: any, locale: any): any,
namespace (): FormatMessage
}
*/
function assign/*:: <T: Object> */ (target/*: T */, source/*: Object */) {
Object.keys(source).forEach(function (key) { target[key] = source[key] })
return target
}
function namespace ()/*: FormatMessage */ {
var formats = assign({}, origFormats)
var currentLocales/*: Locales */ = 'en'
var translations/*: Translations */ = {}
var generateId/*: GenerateId */ = function (pattern) { return pattern }
var missingReplacement/*: Replacement */ = null
var missingTranslation/*: MissingTranslation */ = 'warning'
var types/*: Types */ = {}
function formatMessage (msg/*: Message */, args/*:: ?: Object */, locales/*:: ?: Locales */) {
var pattern = typeof msg === 'string' ? msg : msg.default
var id = (typeof msg === 'object' && msg.id) || generateId(pattern)
var translated = translate(pattern, id, locales || currentLocales)
var format = translated.format || (
translated.format = interpret(parse(translated.message), locales || currentLocales, types)
)
return format(args)
}
formatMessage.rich = function rich (msg/*: Message */, args/*:: ?: Object */, locales/*:: ?: Locales */) {
var pattern = typeof msg === 'string' ? msg : msg.default
var id = (typeof msg === 'object' && msg.id) || generateId(pattern)
var translated = translate(pattern, id, locales || currentLocales)
var format = translated.toParts || (
translated.toParts = interpret.toParts(parse(translated.message, { tagsType: tagsType }), locales || currentLocales, types)
)
return format(args)
}
var tagsType = '<>'
function richType (node/*: any[] */, locales/*: Locales */) {
var style = node[2]
return function (fn, args) {
var props = typeof style === 'object' ? mapObject(style, args) : style
return typeof fn === 'function' ? fn(props) : fn
}
}
types[tagsType] = richType
function mapObject (object/* { [string]: (args?: Object) => any } */, args/*: ?Object */) {
return Object.keys(object).reduce(function (mapped, key) {
mapped[key] = object[key](args)
return mapped
}, {})
}
function translate (pattern/*: string */, id/*: string */, locales/*: Locales */)/*: Translation */ {
var locale = lookupClosestLocale(locales, translations) || 'en'
var messages = translations[locale] || (translations[locale] = {})
var translated = messages[id]
if (typeof translated === 'string') {
translated = messages[id] = { message: translated }
}
if (!translated) {
var message = 'Translation for "' + id + '" in "' + locale + '" is missing'
if (missingTranslation === 'warning') {
/* istanbul ignore else */
if (typeof console !== 'undefined') console.warn(message)
} else if (missingTranslation !== 'ignore') { // 'error'
throw new Error(message)
}
var replacement = typeof missingReplacement === 'function'
? missingReplacement(pattern, id, locale) || pattern
: missingReplacement || pattern
translated = messages[id] = { message: replacement }
}
return translated
}
formatMessage.setup = function setup (opt/*:: ?: Options */) {
opt = opt || {}
if (opt.locale) currentLocales = opt.locale
if ('translations' in opt) translations = opt.translations || {}
if (opt.generateId) generateId = opt.generateId
if ('missingReplacement' in opt) missingReplacement = opt.missingReplacement
if (opt.missingTranslation) missingTranslation = opt.missingTranslation
if (opt.formats) {
if (opt.formats.number) assign(formats.number, opt.formats.number)
if (opt.formats.date) assign(formats.date, opt.formats.date)
if (opt.formats.time) assign(formats.time, opt.formats.time)
}
if (opt.types) {
types = opt.types
types[tagsType] = richType
}
return {
locale: currentLocales,
translations: translations,
generateId: generateId,
missingReplacement: missingReplacement,
missingTranslation: missingTranslation,
formats: formats,
types: types
}
}
formatMessage.number = function (value/*: number */, style/*:: ?: string */, locales/*:: ?: Locales */) {
var options = (style && formats.number[style]) ||
formats.parseNumberPattern(style) ||
formats.number.default
return new Intl.NumberFormat(locales || currentLocales, options).format(value)
}
formatMessage.date = function (value/*:: ?: number | Date */, style/*:: ?: string */, locales/*:: ?: Locales */) {
var options = (style && formats.date[style]) ||
formats.parseDatePattern(style) ||
formats.date.default
return new Intl.DateTimeFormat(locales || currentLocales, options).format(value)
}
formatMessage.time = function (value/*:: ?: number | Date */, style/*:: ?: string */, locales/*:: ?: Locales */) {
var options = (style && formats.time[style]) ||
formats.parseDatePattern(style) ||
formats.time.default
return new Intl.DateTimeFormat(locales || currentLocales, options).format(value)
}
formatMessage.select = function (value/*: any */, options/*: Object */) {
return options[value] || options.other
}
formatMessage.custom = function (placeholder/*: any[] */, locales/*: Locales */, value/*: any */, args/*: Object */) {
if (!(placeholder[1] in types)) return value
return types[placeholder[1]](placeholder, locales)(value, args)
}
formatMessage.plural = plural.bind(null, 'cardinal')
formatMessage.selectordinal = plural.bind(null, 'ordinal')
function plural (
pluralType/*: 'cardinal' | 'ordinal' */,
value/*: number */,
offset/*: any */,
options/*: any */,
locale/*: any */
) {
if (typeof offset === 'object' && typeof options !== 'object') { // offset is optional
locale = options
options = offset
offset = 0
}
var closest = lookupClosestLocale(locale || currentLocales, plurals)
var plural = (closest && plurals[closest][pluralType]) || returnOther
return options['=' + +value] || options[plural(value - offset)] || options.other
}
function returnOther (/*:: n:number */) { return 'other' }
formatMessage.namespace = namespace
return formatMessage
}
module.exports = exports = namespace()