-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
@modrinth/ui will be a package that contains the common messages that are used by two or more packages, as well as utilities for working with localisation, such as `defineMessage` macro and locale files resolver abstraction. It is pre-built in a manner that allows for harsh tree-shaking, meaning the messages that will not be used, should not be included by the bundlers. All messages are also defined using the `defineMessage` macro that cleans the descriptors and pre-parses `defaultMessage` to AST, which serves both as validation, as well as allows its use as fallback even in environments devoid of bundled-in compiler. When all other code parts are switched to use this macro, this might allow to exclude messages files for English language, potentially saving on removed duplication. Please note that currently `defineMessages` does not include `defaultMessage` because it is expected that consumers of `@modrinth/ui` will include and pre-compile extracted messages file. This can be overriden by setting `MODRINTH_I18N_DEFAULT_MESSAGE` environment variable to `1`.
- Loading branch information
Showing
14 changed files
with
1,509 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { globSync } from 'glob' | ||
import { defineBuildConfig } from 'unbuild' | ||
import unimport from 'unimport/unplugin' | ||
import macro from 'unplugin-macros/rollup' | ||
|
||
export default defineBuildConfig({ | ||
entries: globSync(['./src/index.ts', './src/utils/index.ts', './src/common-messages/**/*.ts'], { | ||
posix: true, | ||
}).map((input) => ({ | ||
input, | ||
})), | ||
hooks: { | ||
'rollup:options'(_ctx, options) { | ||
if (Array.isArray(options.output)) { | ||
options.output.forEach((o) => (o.preserveModules = true)) | ||
} | ||
options.plugins ??= [] | ||
|
||
const customPlugins = [ | ||
unimport.rollup({ | ||
imports: [ | ||
{ | ||
from: './src/macros/define-message.ts', | ||
name: 'defineMessage', | ||
with: { type: 'macro' }, | ||
}, | ||
], | ||
}), | ||
macro(), | ||
] | ||
|
||
options.plugins = Array.isArray(options.plugins) | ||
? [...customPlugins, ...options.plugins] | ||
: [...customPlugins, macro(), options.plugins] | ||
}, | ||
}, | ||
declaration: 'compatible', | ||
// Disabling cleaning of dist may be not the brightest idea, but it helps | ||
// avoiding a faulty state where files don't exist (HMR/live-reload concern). | ||
// | ||
// Hey, now it's your responsibility as a dev to clean the dist if you do | ||
// major refactors which involves addition or deletion of files: | ||
// | ||
// `pnpm turbo --filter @modrinth/i18n clean` :) | ||
clean: false, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "@modrinth/i18n", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"import": "./dist/index.mjs" | ||
}, | ||
"./utils": { | ||
"import": "./dist/utils/index.mjs" | ||
} | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"clean": "del-cli dist", | ||
"build": "unbuild", | ||
"intl:extract": "formatjs extract \"src/**/*.ts\" --out-file dist/files/en-US/index.json --format crowdin --preserve-whitespace" | ||
}, | ||
"devDependencies": { | ||
"@formatjs/cli": "^6.2.12", | ||
"@formatjs/icu-messageformat-parser": "^2.7.8", | ||
"glob": "^10.2.7", | ||
"typescript": "^5.2.2", | ||
"unbuild": "^2.0.0", | ||
"unimport": "^3.10.0", | ||
"unplugin-macros": "^0.13.1", | ||
"del-cli": "^5.1.0" | ||
}, | ||
"dependencies": { | ||
"@formatjs/intl-localematcher": "^0.5.4", | ||
"@vintl/nuxt": "^1.9.2", | ||
"@vintl/vintl": "^4.4.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export const cancel = defineMessage({ | ||
id: 'common.actions.cancel', | ||
defaultMessage: 'Cancel', | ||
}) | ||
|
||
export const apply = defineMessage({ | ||
id: 'common.actions.apply', | ||
defaultMessage: 'Apply', | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * as actions from './actions' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * as commonMessages from './common-messages' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { MessageDescriptor } from '@vintl/vintl' | ||
import { parse, ParserOptions as MessageParserOptions } from '@formatjs/icu-messageformat-parser' | ||
|
||
type ProperMessageDescriptor<I extends string> = Omit<MessageDescriptor<I>, 'defaultMessage'> & { | ||
defaultMessage: string | ||
} | ||
|
||
const includeDefaultMessage = ['1', 'true'].includes( | ||
process.env.MODRINTH_I18N_DEFAULT_MESSAGE?.toLowerCase() ?? 'false', | ||
) | ||
|
||
if (includeDefaultMessage) { | ||
console.warn( | ||
"[defineMessage] Default messages are now processed and included in the processed descriptor. This may significantly increase consumers' bundle sizes due to duplication.", | ||
) | ||
} | ||
|
||
/** | ||
* A macro that takes in a static descriptor and emits a JS object representing | ||
* the same descriptor with any fields other than `id` and `defaultMessage` | ||
* deleted. The `defaultMessage` field is also converted to AST, which serves as | ||
* a validation that the message syntax is correct, as well as allows the | ||
* message to be used without bringing compiler to runtime. | ||
* | ||
* @param param0 Message descriptor to process. | ||
* @param opts Options for the message parser. | ||
* @returns JS object with the processed message descriptor. | ||
*/ | ||
export function defineMessage<I extends string>( | ||
this: unknown, | ||
{ id, defaultMessage }: ProperMessageDescriptor<I>, | ||
opts?: MessageParserOptions, | ||
): MessageDescriptor<I> { | ||
if (defaultMessage == null) { | ||
throw new RangeError(`[defineMessage] ${id} is missing 'defaultMessage'`) | ||
} | ||
|
||
if (typeof defaultMessage !== 'string') { | ||
throw new RangeError(`[defineMessage] ${id} 'defaultMessage' must be a string`) | ||
} | ||
|
||
return includeDefaultMessage ? { id, defaultMessage: parse(defaultMessage, opts) } : { id } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { match as matchLocale, type Opts as MatchLocaleOpts } from '@formatjs/intl-localematcher' | ||
import { | ||
normalizeImportSource, | ||
normalizeMessagesImportSource, | ||
normalizeUnspecifiedImportSource, | ||
type ImportSource, | ||
type ImportSourceObject, | ||
type MessagesImportSource, | ||
type MessagesImportSourceObject, | ||
type UnspecifiedImportSource, | ||
type UnspecifiedImportSourceObject, | ||
} from '@vintl/nuxt/options' | ||
|
||
export interface CollectorContext { | ||
addFile(locale: string, file: MessagesImportSource): void | ||
addResource(locale: string, file: ImportSource): void | ||
addImport(locale: string, import_: UnspecifiedImportSource): void | ||
} | ||
|
||
export interface ResolvableEntry { | ||
files: MessagesImportSourceObject[] | ||
resources: ImportSourceObject[] | ||
imports: UnspecifiedImportSourceObject[] | ||
} | ||
|
||
export type Resolver = (locale: string) => ResolvableEntry | undefined | ||
|
||
export async function createLocaleResolver( | ||
collector: (ctx: CollectorContext) => void | Promise<void>, | ||
): Promise<Resolver> { | ||
const entries = new Map<string, ResolvableEntry>() | ||
|
||
function getEntry(locale: string) { | ||
let entry = entries.get(locale) | ||
if (entry == null) { | ||
entry = { files: [], resources: [], imports: [] } | ||
entries.set(locale, entry) | ||
} | ||
return entry | ||
} | ||
|
||
const ctx: CollectorContext = { | ||
addFile(locale, file) { | ||
getEntry(locale).files.push(normalizeMessagesImportSource(file)) | ||
}, | ||
addResource(locale, file) { | ||
getEntry(locale).resources.push(normalizeImportSource(file)) | ||
}, | ||
addImport(locale, import_) { | ||
getEntry(locale).imports.push(normalizeUnspecifiedImportSource(import_)) | ||
}, | ||
} | ||
|
||
await collector(ctx) | ||
|
||
const availableLocales = [...entries.keys()] | ||
|
||
return function resolveLocale(locales: string | string[], opts?: MatchLocaleOpts) { | ||
const requestedLocales = Array.isArray(locales) ? locales : [locales] | ||
const match = matchLocale(requestedLocales, availableLocales, 'en-x-placeholder', opts) | ||
if (match === 'en-x-placeholder') return | ||
return entries.get(match)! | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { createLocaleResolver } from './import-resolver.ts' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2022", | ||
"useDefineForClassFields": true, | ||
"module": "Preserve", | ||
"lib": ["ES2022", "DOM", "DOM.Iterable"], | ||
"skipLibCheck": true, | ||
|
||
"moduleResolution": "bundler", | ||
"allowImportingTsExtensions": true, | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"moduleDetection": "force", | ||
"noEmit": true, | ||
|
||
"strict": true, | ||
"noUnusedLocals": true, | ||
"noUnusedParameters": true, | ||
"noUncheckedIndexedAccess": true, | ||
"noFallthroughCasesInSwitch": true | ||
}, | ||
"include": ["src", "unimport.d.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export {} | ||
declare global { | ||
const defineMessage: (typeof import('./src/macros/define-message.ts'))['defineMessage'] | ||
} |
Oops, something went wrong.