Skip to content

Commit

Permalink
the part where i copy the library to fix a bug
Browse files Browse the repository at this point in the history
  • Loading branch information
KTibow committed Sep 23, 2023
1 parent fff2237 commit 93e909d
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 10 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@
"hls.js": "1.4.12",
"home-assistant-js-websocket": "8.2.0",
"idb-keyval": "6.2.1",
"intl-messageformat-tiny": "^1.0.2",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "1.0.4",
Expand Down
256 changes: 256 additions & 0 deletions src/common/translations/localize-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/*
Modified from MIT code by spurreiter
*/

const FORMATS = {
number: {
integer: {
maximumFractionDigits: 0
},
currency: {
style: 'currency'
},
percent: {
style: 'percent'
}
},
date: {
short: {
month: 'numeric',
day: 'numeric',
year: '2-digit'
},
medium: {
month: 'short',
day: 'numeric',
year: 'numeric'
},
long: {
month: 'long',
day: 'numeric',
year: 'numeric'
},
full: {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric'
}
},
time: {
short: {
hour: 'numeric',
minute: 'numeric',
second: undefined
},
medium: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
},
long: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short'
},
full: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short'
}
}
}

const isEven = (level: number) => level % 2 === 0

const half = (level: number) => Math.floor(level / 2)

const block = ({ part, level, prop: _prop, type: _type }: {
part: string
level: number
prop?: string
type?: string
}) => {
if (isEven(level)) {
return { str: part, level }
}
let [prop, type, opts] = part.split(',').map(s => s.trim())
if (!prop) {
return {}
}
if (!type && _type) {
opts = prop
// @ts-expect-error
prop = _prop
type = _type
}
return { prop, type, opts, level, parts: [] }
}

const splitKeyVals = (opts: string, shorthands: Record<string, any>, base = {}) => opts.split(/([a-zA-Z0-9]+(?:\/\S+|))\s/)
.reduce((curr, keyVal) => {
const [key, val] = keyVal.split('/')
if (val === undefined) {
Object.assign(curr, shorthands[key])
} else {
curr[key] = val
}
return curr
}, { ...base })


export function parse(message: string) {
let part = ''
let level = 0
let sub = 0
const cache = {}
const parts: ReturnType<typeof block>[] = []
const refs = { 0: parts }

for (let i = 0; i < message.length; i++) {
const char = message[i]
switch (char) {
case '{': {
const o = block({ ...cache[level], part, level })
if (o.str || o.prop) refs[sub].push(o)
cache[level] = o
level += 1
sub = half(level)
if (o.parts) refs[sub] = o.parts
part = ''
break
}
case '}': {
const o = block({ ...cache[level], part, level })
if (o.str || o.prop) refs[sub].push(o)
level -= 1
sub = half(level)
part = ''
break
}
default: {
part += char
}
}
}
const o = block({ ...cache[level], part, level })
if (o.str || o.prop) refs[sub].push(o)
return parts
}

const formatPart = (ast: Record<string, any>[], values: Record<string, any>, lng: string) => {
const strs: (string | number)[] = []
const pluralMatches = {}
const selectMatches = {}
const selectOrdinalMatches = {}

for (let i = 0; i < ast.length; i++) {
const { str, prop, type, opts = '', parts } = ast[i]
if (str) {
strs.push(str)
continue
}
if (!prop) {
continue
}
if (!type) {
const str = String(values[prop] ?? `{${prop}}`)
strs.push(str)
continue
}

switch (type) {
case 'number': {
const value = Number(values[prop])
if (!opts) {
strs.push(value)
break
}
const options = splitKeyVals(opts, FORMATS.number)
const str = new Intl.NumberFormat(lng, options).format(value)
strs.push(str)
break
}
case 'plural': {
if (pluralMatches[prop]) continue
if (!opts) {
throw new TypeError(`type "${type}" needs a matcher`)
}
const value = Number(values[prop])
const rule = new Intl.PluralRules(lng).select(value)

if ((opts[0] === '=' && value === Number(opts.slice(1))) ||
(rule === opts && opts[0] !== '=') || opts === 'other'
) {
const str = formatPart(parts, values, lng).replace('#', '' + value)
strs.push(str)
pluralMatches[prop] = true;
} else if (ast[i + 1]?.type !== type) {
throw new TypeError(`type "${type}" needs an "other" match`)
}
break
}
case 'select': {
if (selectMatches[prop]) continue
if (!opts) {
throw new TypeError(`type "${type}" needs a matcher`)
}
const value = values[prop]
if (value === opts || opts === 'other') {
const str = formatPart(parts, values, lng)
strs.push(str)
selectMatches[prop] = true
}
else if (ast[i + 1]?.type !== type) {
throw new TypeError(`type "${type}" needs an "other" match`)
}
break
}
case 'selectordinal': {
if (selectOrdinalMatches[prop]) continue
if (!opts) {
throw new TypeError(`type "${type}" needs a matcher`)
}
const value = Number(values[prop])
const rule = new Intl.PluralRules(lng, { type: 'ordinal' }).select(value)
if ((opts[0] === '=' && value === Number(opts.slice(1))) ||
(opts[0] !== '=' && rule === opts) ||
opts === 'other'
) {
const str = formatPart(parts, values, lng).replace('#', '' + value)
strs.push(str)
selectOrdinalMatches[prop] = true
} else if (ast[i + 1]?.type !== type) {
throw new TypeError(`type "${type}" needs an "other" match`)
}
break
}
case 'date': {
const date = values[prop]
const options = splitKeyVals(opts, FORMATS.date)
const str = new Intl.DateTimeFormat(lng, options).format(date)
strs.push(str)
break
}
case 'time': {
const date = values[prop]
const options = splitKeyVals(opts, FORMATS.time, FORMATS.time.medium)
const str = new Intl.DateTimeFormat(lng, options).format(date)
strs.push(str)
break
}
}
}
return strs.join('')
}

export function format (message = '', values = {}, lng = 'en') {
if (!message.includes('{')) {
return message
}
const ast = parse(message)
const str = formatPart(ast, values, lng)
return str
}
2 changes: 1 addition & 1 deletion src/common/translations/localize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { format } from "intl-messageformat-tiny";
import { format } from "./localize-format";
import { polyfillLocaleData } from "../../resources/locale-data-polyfill";
import { Resources, TranslationDict } from "../../types";

Expand Down
8 changes: 0 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9802,7 +9802,6 @@ __metadata:
husky: 8.0.3
idb-keyval: 6.2.1
instant-mocha: 1.5.2
intl-messageformat-tiny: ^1.0.2
js-yaml: 4.1.0
jszip: 3.10.1
leaflet: 1.9.4
Expand Down Expand Up @@ -10287,13 +10286,6 @@ __metadata:
languageName: node
linkType: hard

"intl-messageformat-tiny@npm:^1.0.2":
version: 1.0.2
resolution: "intl-messageformat-tiny@npm:1.0.2"
checksum: 9ae8a264e9b672dc849385a29c14a57e0c4ddab92e7454f5f424c7dd7db5cd78e862a4ad8e9b002ed1b3c6cc8313acfdc9f6ecfceb42c6373ac71a94952c11ef
languageName: node
linkType: hard

"invert-kv@npm:^1.0.0":
version: 1.0.0
resolution: "invert-kv@npm:1.0.0"
Expand Down

0 comments on commit 93e909d

Please sign in to comment.