mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-11-16 18:10:43 +01:00
wip
This commit is contained in:
parent
56f683d3e0
commit
1e1db5e981
3
compiler/bundle.sh
Executable file
3
compiler/bundle.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
deno bundle index.ts compiler.js
|
125
compiler/compiler.js
Normal file
125
compiler/compiler.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// A script preamble that provides the ability to load a single outfile
|
||||||
|
// TypeScript "bundle" where a main module is loaded which recursively
|
||||||
|
// instantiates all the other modules in the bundle. This code is used to load
|
||||||
|
// bundles when creating snapshots, but is also used when emitting bundles from
|
||||||
|
// Deno cli.
|
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {(name: string, deps: ReadonlyArray<string>, factory: (...deps: any[]) => void) => void=}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
let define;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {(mod: string) => any=}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
let instantiate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback Factory
|
||||||
|
* @argument {...any[]} args
|
||||||
|
* @returns {object | void}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef ModuleMetaData
|
||||||
|
* @property {ReadonlyArray<string>} dependencies
|
||||||
|
* @property {(Factory | object)=} factory
|
||||||
|
* @property {object} exports
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
/**
|
||||||
|
* @type {Map<string, ModuleMetaData>}
|
||||||
|
*/
|
||||||
|
const modules = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundles in theory can support "dynamic" imports, but for internal bundles
|
||||||
|
* we can't go outside to fetch any modules that haven't been statically
|
||||||
|
* defined.
|
||||||
|
* @param {string[]} deps
|
||||||
|
* @param {(...deps: any[]) => void} resolve
|
||||||
|
* @param {(err: any) => void} reject
|
||||||
|
*/
|
||||||
|
const require = (deps, resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (deps.length !== 1) {
|
||||||
|
throw new TypeError("Expected only a single module specifier.");
|
||||||
|
}
|
||||||
|
if (!modules.has(deps[0])) {
|
||||||
|
throw new RangeError(`Module "${deps[0]}" not defined.`);
|
||||||
|
}
|
||||||
|
resolve(getExports(deps[0]));
|
||||||
|
} catch (e) {
|
||||||
|
if (reject) {
|
||||||
|
reject(e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
define = (id, dependencies, factory) => {
|
||||||
|
if (modules.has(id)) {
|
||||||
|
throw new RangeError(`Module "${id}" has already been defined.`);
|
||||||
|
}
|
||||||
|
modules.set(id, {
|
||||||
|
dependencies,
|
||||||
|
factory,
|
||||||
|
exports: {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
function getExports(id) {
|
||||||
|
const module = modules.get(id);
|
||||||
|
if (!module) {
|
||||||
|
// because `$deno$/ts_global.d.ts` looks like a real script, it doesn't
|
||||||
|
// get erased from output as an import, but it doesn't get defined, so
|
||||||
|
// we don't have a cache for it, so because this is an internal bundle
|
||||||
|
// we can just safely return an empty object literal.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!module.factory) {
|
||||||
|
return module.exports;
|
||||||
|
} else if (module.factory) {
|
||||||
|
const { factory, exports } = module;
|
||||||
|
delete module.factory;
|
||||||
|
if (typeof factory === "function") {
|
||||||
|
const dependencies = module.dependencies.map(id => {
|
||||||
|
if (id === "require") {
|
||||||
|
return require;
|
||||||
|
} else if (id === "exports") {
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
return getExports(id);
|
||||||
|
});
|
||||||
|
factory(...dependencies);
|
||||||
|
} else {
|
||||||
|
Object.assign(exports, factory);
|
||||||
|
}
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiate = dep => {
|
||||||
|
define = undefined;
|
||||||
|
const result = getExports(dep);
|
||||||
|
// clean up, or otherwise these end up in the runtime environment
|
||||||
|
instantiate = undefined;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
console.log('foo');
|
||||||
|
|
||||||
|
instantiate("file:///Users/kaisermann/Projects/open-source/svelte-i18n/compiler/index");
|
2
compiler/deps/parser.ts
Normal file
2
compiler/deps/parser.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// @deno-types="https://unpkg.com/intl-messageformat-parser@3.6.3/lib/index.d.ts"
|
||||||
|
export * from 'https://unpkg.com/intl-messageformat-parser@3.6.3/lib/index.js'
|
270
compiler/formatters.ts
Normal file
270
compiler/formatters.ts
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import {
|
||||||
|
convertNumberSkeletonToNumberFormatOptions,
|
||||||
|
isArgumentElement,
|
||||||
|
isDateElement,
|
||||||
|
isDateTimeSkeleton,
|
||||||
|
isLiteralElement,
|
||||||
|
isNumberElement,
|
||||||
|
isNumberSkeleton,
|
||||||
|
isPluralElement,
|
||||||
|
isPoundElement,
|
||||||
|
isSelectElement,
|
||||||
|
isTimeElement,
|
||||||
|
MessageFormatElement,
|
||||||
|
parseDateTimeSkeleton,
|
||||||
|
} from './deps/parser.ts'
|
||||||
|
|
||||||
|
export interface Formats {
|
||||||
|
number: Record<string, Intl.NumberFormatOptions>
|
||||||
|
date: Record<string, Intl.DateTimeFormatOptions>
|
||||||
|
time: Record<string, Intl.DateTimeFormatOptions>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormatterCache {
|
||||||
|
number: Record<string, Intl.NumberFormat>
|
||||||
|
dateTime: Record<string, Intl.DateTimeFormat>
|
||||||
|
pluralRules: Record<string, Intl.PluralRules>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Formatters {
|
||||||
|
getNumberFormat(
|
||||||
|
...args: ConstructorParameters<typeof Intl.NumberFormat>
|
||||||
|
): Intl.NumberFormat
|
||||||
|
getDateTimeFormat(
|
||||||
|
...args: ConstructorParameters<typeof Intl.DateTimeFormat>
|
||||||
|
): Intl.DateTimeFormat
|
||||||
|
getPluralRules(
|
||||||
|
...args: ConstructorParameters<typeof Intl.PluralRules>
|
||||||
|
): Intl.PluralRules
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum PART_TYPE {
|
||||||
|
literal,
|
||||||
|
argument,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LiteralPart {
|
||||||
|
type: PART_TYPE.literal
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArgumentPart {
|
||||||
|
type: PART_TYPE.argument
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MessageFormatPart = LiteralPart | ArgumentPart
|
||||||
|
|
||||||
|
export type PrimitiveType = string | number | boolean | null | undefined | Date
|
||||||
|
|
||||||
|
class FormatError extends Error {
|
||||||
|
public readonly variableId?: string
|
||||||
|
constructor(msg?: string, variableId?: string) {
|
||||||
|
super(msg)
|
||||||
|
this.variableId = variableId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeLiteral(parts: MessageFormatPart[]): MessageFormatPart[] {
|
||||||
|
if (parts.length < 2) {
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
return parts.reduce((all, part) => {
|
||||||
|
const lastPart = all[all.length - 1]
|
||||||
|
if (
|
||||||
|
!lastPart ||
|
||||||
|
lastPart.type !== PART_TYPE.literal ||
|
||||||
|
part.type !== PART_TYPE.literal
|
||||||
|
) {
|
||||||
|
all.push(part)
|
||||||
|
} else {
|
||||||
|
lastPart.value += part.value
|
||||||
|
}
|
||||||
|
return all
|
||||||
|
}, [] as MessageFormatPart[])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(skeleton): add skeleton support
|
||||||
|
export function formatToParts(
|
||||||
|
els: MessageFormatElement[],
|
||||||
|
locales: string | string[],
|
||||||
|
formatters: Formatters,
|
||||||
|
formats: Formats,
|
||||||
|
values?: Record<string, any>,
|
||||||
|
currentPluralValue?: number,
|
||||||
|
// For debugging
|
||||||
|
originalMessage?: string
|
||||||
|
): MessageFormatPart[] {
|
||||||
|
// Hot path for straight simple msg translations
|
||||||
|
if (els.length === 1 && isLiteralElement(els[0])) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: PART_TYPE.literal,
|
||||||
|
value: els[0].value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const result: MessageFormatPart[] = []
|
||||||
|
for (const el of els) {
|
||||||
|
// Exit early for string parts.
|
||||||
|
if (isLiteralElement(el)) {
|
||||||
|
result.push({
|
||||||
|
type: PART_TYPE.literal,
|
||||||
|
value: el.value,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: should this part be literal type?
|
||||||
|
// Replace `#` in plural rules with the actual numeric value.
|
||||||
|
if (isPoundElement(el)) {
|
||||||
|
if (typeof currentPluralValue === 'number') {
|
||||||
|
result.push({
|
||||||
|
type: PART_TYPE.literal,
|
||||||
|
value: formatters.getNumberFormat(locales).format(currentPluralValue),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value: varName } = el
|
||||||
|
|
||||||
|
// Enforce that all required values are provided by the caller.
|
||||||
|
if (!(values && varName in values)) {
|
||||||
|
throw new FormatError(
|
||||||
|
`The intl string context variable "${varName}" was not provided to the string "${originalMessage}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = values[varName]
|
||||||
|
if (isArgumentElement(el)) {
|
||||||
|
if (!value || typeof value === 'string' || typeof value === 'number') {
|
||||||
|
value =
|
||||||
|
typeof value === 'string' || typeof value === 'number'
|
||||||
|
? String(value)
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
type: PART_TYPE.argument,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively format plural and select parts' option — which can be a
|
||||||
|
// nested pattern structure. The choosing of the option to use is
|
||||||
|
// abstracted-by and delegated-to the part helper object.
|
||||||
|
if (isDateElement(el)) {
|
||||||
|
const style =
|
||||||
|
typeof el.style === 'string' ? formats.date[el.style] : undefined
|
||||||
|
result.push({
|
||||||
|
type: PART_TYPE.literal,
|
||||||
|
value: formatters
|
||||||
|
.getDateTimeFormat(locales, style)
|
||||||
|
.format(value as number),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (isTimeElement(el)) {
|
||||||
|
const style =
|
||||||
|
typeof el.style === 'string'
|
||||||
|
? formats.time[el.style]
|
||||||
|
: isDateTimeSkeleton(el.style)
|
||||||
|
? parseDateTimeSkeleton(el.style.pattern)
|
||||||
|
: undefined
|
||||||
|
result.push({
|
||||||
|
type: PART_TYPE.literal,
|
||||||
|
value: formatters
|
||||||
|
.getDateTimeFormat(locales, style)
|
||||||
|
.format(value as number),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (isNumberElement(el)) {
|
||||||
|
const style =
|
||||||
|
typeof el.style === 'string'
|
||||||
|
? formats.number[el.style]
|
||||||
|
: isNumberSkeleton(el.style)
|
||||||
|
? convertNumberSkeletonToNumberFormatOptions(el.style.tokens)
|
||||||
|
: undefined
|
||||||
|
result.push({
|
||||||
|
type: PART_TYPE.literal,
|
||||||
|
value: formatters
|
||||||
|
.getNumberFormat(locales, style)
|
||||||
|
.format(value as number),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (isSelectElement(el)) {
|
||||||
|
const opt = el.options[value as string] || el.options.other
|
||||||
|
if (!opt) {
|
||||||
|
throw new RangeError(
|
||||||
|
`Invalid values for "${
|
||||||
|
el.value
|
||||||
|
}": "${value}". Options are "${Object.keys(el.options).join('", "')}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result.push(
|
||||||
|
...formatToParts(opt.value, locales, formatters, formats, values)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (isPluralElement(el)) {
|
||||||
|
let opt = el.options[`=${value}`]
|
||||||
|
if (!opt) {
|
||||||
|
if (!Intl.PluralRules) {
|
||||||
|
throw new FormatError(`Intl.PluralRules is not available in this environment.
|
||||||
|
Try polyfilling it using "@formatjs/intl-pluralrules"
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
const rule = formatters
|
||||||
|
.getPluralRules(locales, { type: el.pluralType })
|
||||||
|
.select((value as number) - (el.offset || 0))
|
||||||
|
opt = el.options[rule] || el.options.other
|
||||||
|
}
|
||||||
|
if (!opt) {
|
||||||
|
throw new RangeError(
|
||||||
|
`Invalid values for "${
|
||||||
|
el.value
|
||||||
|
}": "${value}". Options are "${Object.keys(el.options).join('", "')}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result.push(
|
||||||
|
...formatToParts(
|
||||||
|
opt.value,
|
||||||
|
locales,
|
||||||
|
formatters,
|
||||||
|
formats,
|
||||||
|
values,
|
||||||
|
value - (el.offset || 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergeLiteral(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatToString(
|
||||||
|
els: MessageFormatElement[],
|
||||||
|
locales: string | string[],
|
||||||
|
formatters: Formatters,
|
||||||
|
formats: Formats,
|
||||||
|
values?: Record<string, PrimitiveType>,
|
||||||
|
// For debugging
|
||||||
|
originalMessage?: string
|
||||||
|
): string {
|
||||||
|
const parts = formatToParts(
|
||||||
|
els,
|
||||||
|
locales,
|
||||||
|
formatters,
|
||||||
|
formats,
|
||||||
|
values,
|
||||||
|
undefined,
|
||||||
|
originalMessage
|
||||||
|
)
|
||||||
|
// Hot path for straight simple msg translations
|
||||||
|
if (parts.length === 1) {
|
||||||
|
return parts[0].value
|
||||||
|
}
|
||||||
|
return parts.reduce((all, part) => (all += part.value), '')
|
||||||
|
}
|
9
compiler/index.ts
Normal file
9
compiler/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { parse } from './deps/parser.ts'
|
||||||
|
|
||||||
|
const log_json = (o: object) => console.log(JSON.stringify(o, null, 2))
|
||||||
|
const ast = parse(`{taxableArea, select,
|
||||||
|
yes {An additional {taxRate, number, percent} tax will be collected.}
|
||||||
|
other {No taxes apply.}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
log_json(ast)
|
Loading…
Reference in New Issue
Block a user