Add translations support (#116)

This commit is contained in:
Sasha Sorokin
2023-11-11 23:03:58 +01:00
committed by GitHub
parent 1c18563dfb
commit 591ce0894e
11 changed files with 838 additions and 130 deletions

View File

@@ -1,7 +1,11 @@
import { resolve } from 'path'
import { resolve, basename } from 'path'
import svgLoader from 'vite-svg-loader'
import eslintPlugin from 'vite-plugin-eslint'
import { icuMessages } from '@vintl/unplugin/vite'
import virtual from '@rollup/plugin-virtual'
import { globSync } from 'glob'
/** @type {import('vitepress').SiteConfig} */
export default {
title: 'Omorphia',
description: 'A components library used for Modrinth.',
@@ -80,11 +84,40 @@ export default {
},
}),
eslintPlugin(),
icuMessages({
filter: (id) => id.endsWith('.json?messages'),
pluginsWrapping: true,
}),
virtual({
'@modrinth/omorphia-dev/locales/index.js': (() => {
const localeDirs = globSync('../../locales/*', { cwd: __dirname, absolute: true })
let output = 'export const localeDefinitions = Object.create(null);\n'
for (const localeDir of localeDirs) {
const tag = basename(localeDir)
output += `localeDefinitions[${JSON.stringify(tag)}] = {\n`
output += '\tasync importFunction() {\n'
output += `\t\tconst messages = Object.create(null);\n`
for (const filePath of globSync('*', { cwd: localeDir, absolute: true })) {
const fileName = basename(filePath)
if (fileName === 'index.json') {
output += `\t\tObject.assign(messages, await import(${JSON.stringify(
`${filePath}?messages`
)}).then((mod) => mod['default']));\n`
}
}
output += '\t\treturn { messages }\n'
output += '\t},\n'
output += '}\n'
}
return output
})(),
}),
],
resolve: {
alias: {
'@': resolve(__dirname, '../../lib'),
omorphia: resolve(__dirname, '../../lib'),
'@formatjs/icu-messageformat-parser': '@formatjs/icu-messageformat-parser/lib/no-parser',
},
dedupe: ['vue'],
},

15
docs/.vitepress/env.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
/// <reference types="vite/client" />
declare module '@modrinth/omorphia-dev/locales/index.js' {
interface LocaleExport {
messages: Record<string, any[]>
}
interface LocaleDefinition {
importFunction(): Promise<LocaleExport>
}
const localeDefinitions: Partial<Record<string, LocaleDefinition>>
export { localeDefinitions }
}

View File

@@ -0,0 +1,73 @@
<script setup lang="ts">
import { useVIntl } from '@vintl/vintl'
import { DropdownSelect } from 'omorphia'
import { computed, ref } from 'vue'
const { $locales, $config, changeLocale } = useVIntl()
const getLocaleDisplayName = (() => {
const cache = new Map<string, Intl.DisplayNames>()
return function getLocaleDisplayName(locale: string) {
let displayNames = cache.get(locale)
if (displayNames == null) {
displayNames = new Intl.DisplayNames(locale, {
type: 'language',
languageDisplay: 'standard',
})
cache.set(locale, displayNames)
}
return displayNames.of(locale)
}
})()
const isChanging = ref(false)
const currentLocale = computed({
get() {
return $config.locale
},
async set(value) {
if (isChanging.value) return
try {
isChanging.value = true
await changeLocale(value)
} finally {
isChanging.value = false
}
},
})
</script>
<template>
<div class="LanguageSwitcher">
<h2 class="title">Playground language</h2>
<DropdownSelect
class="locale-dropdown"
name="locale"
v-model="currentLocale"
placeholder="Change language"
:disabled="isChanging"
:options="Array.from($locales).map(([{ tag }]) => tag)"
:display-name="(locale: string) => getLocaleDisplayName(locale)"
/>
</div>
</template>
<style scoped>
.LanguageSwitcher {
padding-block: 18px;
border-bottom: 1px solid var(--vp-c-divider);
}
.LanguageSwitcher .title {
font-weight: 700;
font-size: 14px;
color: var(--vp-c-text-1);
}
.LanguageSwitcher .locale-dropdown {
width: 200px;
font-size: 14px;
}
</style>

View File

@@ -1,14 +1,50 @@
import DefaultTheme from 'vitepress/theme'
import { localeDefinitions } from '@modrinth/omorphia-dev/locales/index.js'
import { createPlugin } from '@vintl/vintl/plugin'
import Omorphia from 'omorphia'
import DefaultTheme from 'vitepress/theme'
import { createVNode } from 'vue'
import DemoContainer from './DemoContainer.vue'
import LanguageSwitcher from './LanguageSwitcher.vue'
import './compat.scss'
import './style.scss'
/** @type {import('vitepress').Theme} */
export default {
...DefaultTheme,
enhanceApp(ctx) {
ctx.app.use(Omorphia)
ctx.app.component('DemoContainer', DemoContainer)
ctx.app.use(
createPlugin({
controllerOpts: {
locales: Object.keys(localeDefinitions).map((tag) => ({ tag })),
listen: {
async localeload(event) {
const locale = event.locale.tag
if (!Object.hasOwn(localeDefinitions, locale)) {
throw new Error(`Unknown locale: ${locale}`)
}
try {
const { messages } = await localeDefinitions[locale].importFunction()
event.addMessages(messages)
} catch (err) {
console.error(`Failed to load locale: ${locale}`, err)
}
},
},
defaultMessageOrder: ['locale', 'descriptor'],
},
globalMixin: false,
})
)
},
Layout() {
return createVNode(DefaultTheme.Layout, null, {
'sidebar-nav-before'() {
return createVNode(LanguageSwitcher)
},
})
},
}