提交 e347bcd8 编写于 作者: Q qiang

Merge branch 'next' of github.com:dcloudio/uni-app into next

......@@ -50,6 +50,7 @@ declare var __UNI_FEATURE_NAVIGATIONBAR__: boolean
declare var __UNI_FEATURE_NAVIGATIONBAR_BUTTONS__: boolean
declare var __UNI_FEATURE_NAVIGATIONBAR_SEARCHINPUT__: boolean
declare var __UNI_FEATURE_NAVIGATIONBAR_TRANSPARENT__: boolean
declare var __UNI_FEATURE_I18N_LOCALE__: boolean
declare var __UNI_FEATURE_I18N_EN__: boolean
declare var __UNI_FEATURE_I18N_ES__: boolean
declare var __UNI_FEATURE_I18N_FR__: boolean
......
......@@ -32,6 +32,6 @@
"compression": "^1.7.4",
"cypress": "^7.3.0",
"serve-static": "^1.14.1",
"vite": "^2.5.1"
"vite": "2.5.1"
}
}
......@@ -54,10 +54,10 @@
lodash.once "^4.1.1"
"@dcloudio/uni-app@../../uni-app":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/uni-cli-shared@../../uni-cli-shared":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
dependencies:
"@rollup/pluginutils" "^4.1.1"
chokidar "^3.5.2"
......@@ -73,19 +73,19 @@
xregexp "3.1.0"
"@dcloudio/uni-cloud@../../uni-cloud":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/uni-components@../../uni-components":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/uni-h5-vite@../../uni-h5-vite":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/uni-h5-vue@../../uni-h5-vue":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/uni-h5@../../uni-h5":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
dependencies:
localstorage-polyfill "^1.0.1"
pako "^2.0.3"
......@@ -93,16 +93,16 @@
xmlhttprequest "^1.8.0"
"@dcloudio/uni-i18n@../../uni-i18n":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/uni-shared@../../uni-shared":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/uni-stat@../../uni-stat":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
"@dcloudio/vite-plugin-uni@../../vite-plugin-uni":
version "3.0.0-alpha-3000020210813002"
version "3.0.0-alpha-3000020210827004"
dependencies:
"@rollup/pluginutils" "^4.1.0"
autoprefixer "^10.2.5"
......@@ -171,9 +171,9 @@
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
"@types/node@*":
version "16.7.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.2.tgz#0465a39b5456b61a04d98bd5545f8b34be340cb7"
integrity sha512-TbG4TOx9hng8FKxaVrCisdaxKxqEwJ3zwHoCWXZ0Jw6mnvTInpaB99/2Cy4+XxpXtjNv9/TgfGSvZFyfV/t8Fw==
version "16.7.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.8.tgz#2448be5f24fe6b77114632b6350fcd219334651e"
integrity sha512-8upnoQU0OPzbIkm+ZMM0zCeFCkw2s3mS0IWdx0+AAaWqm4fkBb0UJp8Edl7FVKRamYbpJC/aVsHpKWBIbiC7Zg==
"@types/node@^14.14.31":
version "14.17.12"
......@@ -412,12 +412,12 @@ at-least-node@^1.0.0:
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
autoprefixer@^10.2.5:
version "10.3.2"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.3.2.tgz#836e4b4f59eb6876c41012c1c937be74035f3ec8"
integrity sha512-RHKq0YCvhxAn9987n0Gl6lkzLd39UKwCkUPMFE0cHhxU0SvcTjBxWG/CtkZ4/HvbqK9U5V8j03nAcGBlX3er/Q==
version "10.3.3"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.3.3.tgz#4bac89c74ef98e6a40fe1c5b76c0d1c91db153ce"
integrity sha512-yRzjxfnggrP/+qVHlUuZz5FZzEbkT+Yt0/Df6ScEMnbbZBLzYB2W0KLxoQCW+THm1SpOsM1ZPcTHAwuvmibIsQ==
dependencies:
browserslist "^4.16.8"
caniuse-lite "^1.0.30001251"
caniuse-lite "^1.0.30001252"
colorette "^1.3.0"
fraction.js "^4.1.1"
normalize-range "^0.1.2"
......@@ -532,10 +532,10 @@ cachedir@^2.3.0:
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==
caniuse-lite@^1.0.30001251:
version "1.0.30001251"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz#6853a606ec50893115db660f82c094d18f096d85"
integrity sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==
caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001252:
version "1.0.30001252"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz#cb16e4e3dafe948fc4a9bb3307aea054b912019a"
integrity sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==
caseless@~0.12.0:
version "0.12.0"
......@@ -843,9 +843,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.811:
version "1.3.818"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.818.tgz#32ed024fa8316e5d469c96eecbea7d2463d80085"
integrity sha512-c/Z9gIr+jDZAR9q+mn40hEc1NharBT+8ejkarjbCDnBNFviI6hvcC5j2ezkAXru//bTnQp5n6iPi0JA83Tla1Q==
version "1.3.824"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.824.tgz#9f85cc826c70b12180009d461e3b19c8121a56d2"
integrity sha512-Fk+5aD0HDi9i9ZKt9n2VPOZO1dQy7PV++hz2wJ/KIn+CvVfu4fny39squHtyVDPuHNuoJGAZIbuReEklqYIqfA==
emoji-regex@^8.0.0:
version "8.0.0"
......@@ -877,9 +877,9 @@ enquirer@^2.3.6:
ansi-colors "^4.1.1"
esbuild@^0.12.17:
version "0.12.22"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.22.tgz#6031a1257b8d0307d306bed673b79c3668607f51"
integrity sha512-yWCr9RoFehpqoe/+MwZXJpYOEIt7KOEvNnjIeMZpMSyQt+KCBASM3y7yViiN5dJRphf1wGdUz1+M4rTtWd/ulA==
version "0.12.24"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.24.tgz#21966fad25a80f368ed308101e88102bce0dc68f"
integrity sha512-C0ibY+HsXzYB6L/pLWEiWjMpghKsIc58Q5yumARwBQsHl9DXPakW+5NI/Y9w4YXiz0PEP6XTGTT/OV4Nnsmb4A==
escalade@^3.1.1:
version "3.1.1"
......@@ -2309,10 +2309,10 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
vite@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.5.1.tgz#953c71a034c07b3ae0448d57664ec9c6862f23a8"
integrity sha512-FwmLbbz8MB1pBs9dKoRDgpiqoijif8hSK1+NNUYc12/cnf+pM2UFhhQ1rcpXgbMhm/5c2USZdVAf0FSkSxaFDA==
vite@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.5.2.tgz#3963a4ec1e6ecae49359eddfdd67f6cb1e6e07a1"
integrity sha512-JK5uhiVyMqHiAJbgBa8rCvpP8bEhAE9dKDv1gCmP+EUP2FSPmEeW3WXlCXauPB3MDa8behPW+ntyNXqnGaxslg==
dependencies:
esbuild "^0.12.17"
postcss "^8.3.6"
......
......@@ -71,6 +71,8 @@ declare namespace UniApp {
onReady: (fn: Function) => void
serviceReady: boolean
locale: string
fallbackLocale: string
locales: Record<string, Record<string, string>>
}
interface UniRoute {
......@@ -188,6 +190,7 @@ declare namespace UniApp {
interface PageRouteMeta extends PagesJsonPageStyle {
id?: number
route: string
i18n?: boolean
isQuit?: boolean
isEntry?: boolean
isTabBar?: boolean
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
import { TEMP_PATH } from '../constants'
import { warpPlusErrorCallback } from '../../../helpers/plus'
import { getFileName } from '../../../helpers/file'
import {
API_TYPE_CHOOSE_IMAGE,
API_CHOOSE_IMAGE,
......@@ -26,6 +27,30 @@ function getFileInfo(filePath: string): Promise<PlusIoMetadata> {
})
}
function compressImage(tempFilePath: string): Promise<string> {
const dst = `${TEMP_PATH}/compressed/${Date.now()}_${getFileName(
tempFilePath
)}`
return new Promise((resolve) => {
plus.nativeUI.showWaiting()
plus.zip.compressImage(
{
src: tempFilePath,
dst,
overwrite: true,
},
() => {
plus.nativeUI.closeWaiting()
resolve(dst)
},
() => {
plus.nativeUI.closeWaiting()
resolve(tempFilePath)
}
)
})
}
type File = {
path: string
size: number
......@@ -61,11 +86,33 @@ export const chooseImage = defineAsyncApi<API_TYPE_CHOOSE_IMAGE>(
function openCamera() {
const camera = plus.camera.getCamera()
camera.captureImage((path) => successCallback([path]), errorCallback, {
filename: TEMP_PATH + '/camera/',
resolution: 'high',
crop,
})
camera.captureImage(
(path) => {
// fix By Lxh 暂时添加拍照压缩逻辑,等客户端增加逻辑后修改
// 判断是否需要压缩
if (sizeType && sizeType.includes('compressed')) {
return getFileInfo(path)
.then(({ size }) => {
// 压缩阈值 0.5 兆
const THRESHOLD = 1024 * 1024 * 0.5
return size && size > THRESHOLD
? compressImage(path).then((dstPath) =>
successCallback([dstPath])
)
: successCallback([path])
})
.catch(errorCallback)
}
return successCallback([path])
},
errorCallback,
{
filename: TEMP_PATH + '/camera/',
resolution: 'high',
crop,
}
)
}
function openAlbum() {
......
......@@ -24,11 +24,15 @@ export type Define = typeof def
export type Require = typeof req
export type Exports = Record<string, any>
export function def(name: string, deps: string[], definition: Function) {
req(deps, () => resolve(name, definition()))
export function def(
name: string,
deps: string[],
definition: (...args: any[]) => void
) {
req(deps, () => resolve(name))
}
export function req(modules: string[], definition: Function) {
export function req(modules: string[], definition: (...args: any[]) => void) {
Promise.all(modules.map(deps)).then((result) =>
definition.apply(null, result)
)
......
......@@ -2,16 +2,35 @@ import {
isServiceNativeTag,
isServiceCustomElement,
} from '@dcloudio/uni-shared'
import { UniVitePlugin } from '@dcloudio/uni-cli-shared'
import { compileI18nJsonStr } from '@dcloudio/uni-i18n'
import {
UniVitePlugin,
initI18nOptions,
getFallbackLocale,
} from '@dcloudio/uni-cli-shared'
export function uniOptions(): UniVitePlugin['uni'] {
return {
copyOptions() {
const inputDir = process.env.UNI_INPUT_DIR
const outputDir = process.env.UNI_OUTPUT_DIR
return {
assets: [
'androidPrivacy.json',
'hybrid/html/**/*',
'uni_modules/*/hybrid/html/**/*',
assets: ['hybrid/html/**/*', 'uni_modules/*/hybrid/html/**/*'],
targets: [
{
src: 'androidPrivacy.json',
dest: outputDir,
transform(source) {
const options = initI18nOptions(
inputDir,
getFallbackLocale(inputDir)
)
if (!options) {
return
}
return compileI18nJsonStr(source.toString(), options)
},
},
],
}
},
......
......@@ -21,10 +21,10 @@ export function uniMainJsPlugin() {
}
function createApp(code: string) {
return `const __app__=createApp().app;__app__._component.mpType='app';__app__._component.render=()=>{};__app__.use(uni.__vuePlugin).mount("#app");${code.replace(
return `${code.replace(
'createSSRApp',
'createVueApp as createSSRApp'
)}`
)};const __app__=createApp().app;__app__._component.mpType='app';__app__._component.render=()=>{};__app__.use(uni.__vuePlugin).mount("#app");`
}
function createLegacyApp(code: string) {
......
import { normalizeI18nLocale } from '../src/i18n'
describe('normalizeI18nLocale', () => {
test('specifying locale', () => {
expect(normalizeI18nLocale({ 'zh-Hans': {}, fr: {} }, 'fr')).toBe('fr')
})
test('fallback en', () => {
expect(normalizeI18nLocale({ 'zh-Hans': {}, en: {} }, 'fr')).toBe('en')
})
test('fallback zh-Hans', () => {
expect(normalizeI18nLocale({ 'zh-Hans': {}, es: {} })).toBe('zh-Hans')
})
test('fallback zh-Hant', () => {
expect(normalizeI18nLocale({ 'zh-Hant': {}, es: {} })).toBe('zh-Hant')
})
test('fallback first locale', () => {
expect(normalizeI18nLocale({ fr: {}, es: {} })).toBe('fr')
})
})
import fs from 'fs'
import path from 'path'
import { I18N_JSON_DELIMITERS } from '@dcloudio/uni-shared'
export function initI18nOptions(inputDir: string, fallbackLocale?: string) {
const locales = initLocales(path.resolve(inputDir, 'locale'))
if (!Object.keys(locales).length) {
return
}
const locale = normalizeI18nLocale(locales, fallbackLocale)
return {
locale,
locales,
delimiters: I18N_JSON_DELIMITERS,
}
}
function initLocales(dir: string) {
if (!fs.existsSync(dir)) {
return {}
}
return fs.readdirSync(dir).reduce((res, filename) => {
if (path.extname(filename) === '.json') {
try {
res[path.basename(filename).replace('.json', '')] = JSON.parse(
fs.readFileSync(path.join(dir, filename), 'utf8')
)
} catch (e) {}
}
return res
}, {} as Record<string, Record<string, string>>)
}
const defaultFallbackLocale = 'en'
// specifying locale > en > zh-Hans > zh-Hant > first locale
export function normalizeI18nLocale(
locales: Record<string, Record<string, string>>,
locale: string = defaultFallbackLocale
) {
if (locales[locale]) {
return locale
}
return (
['en', 'zh-Hans', 'zh-Hant'].find((n) => locales[n]) ||
Object.keys(locales)[0] ||
defaultFallbackLocale
)
}
......@@ -3,6 +3,7 @@ export * from './env'
export * from './hbx'
export * from './logs'
export * from './ssr'
export * from './i18n'
export * from './deps'
export * from './json'
export * from './vite'
......
import { compileI18nJsonStr } from '@dcloudio/uni-i18n'
import { M } from '../../../messages'
import { initI18nOptions } from '../../../i18n'
export function initI18n(
manifestJson: Record<string, any>,
userManifestJson: Record<string, any>
) {
const i18nOptions = initI18nOptions(
process.env.UNI_INPUT_DIR,
userManifestJson.fallbackLocale
)
let fallbackLocale: string | undefined = undefined
if (i18nOptions) {
fallbackLocale = i18nOptions.locale
if (!userManifestJson.fallbackLocale) {
console.warn(
M['i18n.fallbackLocale.missing'].replace('{locale}', fallbackLocale)
)
} else if (userManifestJson.fallbackLocale !== fallbackLocale) {
console.warn(
M['i18n.fallbackLocale.unmatch'].replace(
'{locale}',
userManifestJson.fallbackLocale
)
)
}
if (manifestJson.plus.tabBar) {
manifestJson.plus.tabBar = JSON.parse(
compileI18nJsonStr(
JSON.stringify(manifestJson.plus.tabBar),
i18nOptions
)
)
}
manifestJson.fallbackLocale = fallbackLocale
}
}
......@@ -10,6 +10,7 @@ import { initConfusion } from './confusion'
import { initUniApp } from './uniApp'
import { initLaunchwebview } from './launchwebview'
import { initTabBar } from './tabBar'
import { initI18n } from './i18n'
export function normalizeAppManifestJson(
userManifestJson: Record<string, any>,
......@@ -32,6 +33,7 @@ export function normalizeAppManifestJson(
manifestJson,
pagesJson
)
initI18n(manifestJson, userManifestJson)
return manifestJson
}
......
......@@ -15,7 +15,7 @@ export function initRecursiveMerge(
name: userManifestJson.versionName,
code: userManifestJson.versionCode,
},
language: userManifestJson.locale,
locale: userManifestJson.locale,
},
{ plus: userManifestJson['app-plus'] }
)
......
......@@ -52,3 +52,8 @@ export function getUniStatistics(inputDir: string, platform: UniApp.PLATFORM) {
manifest[platform] && manifest[platform].uniStatistics
)
}
export function getFallbackLocale(inputDir: string) {
const manifest = parseManifestJsonOnce(inputDir)
return manifest.fallbackLocale
}
......@@ -12,4 +12,8 @@ export const M = {
'dev.watching.end.files': 'DONE Build complete. FILES:{files}',
'stat.warn.appid':
'当前应用未配置Appid,无法使用uni统计,详情参考:https://ask.dcloud.net.cn/article/36303',
'i18n.fallbackLocale.missing':
'当前应用未在manifest.json配置fallbackLocale,默认使用:{locale}',
'i18n.fallbackLocale.unmatch':
'当前应用配置的fallbackLocale为:{locale},但locale目录缺少该语言文件',
}
......@@ -3,7 +3,14 @@ import path from 'path'
import { ConfigEnv } from 'vite'
import { extend, isArray, isString } from '@vue/shared'
interface ProjectFeatures {}
interface ProjectFeatures {
i18nLocale: boolean
i18nEn: boolean
i18nEs: boolean
i18nFr: boolean
i18nZhHans: boolean
i18nZhHant: boolean
}
interface PagesFeatures {
nvue: boolean
pages: boolean
......@@ -25,18 +32,26 @@ interface ManifestFeatures {
promise: boolean
longpress: boolean
routerMode: '"hash"' | '"history"'
i18nEn: boolean
i18nEs: boolean
i18nFr: boolean
i18nZhHans: boolean
i18nZhHant: boolean
vueOptionsApi: boolean
vueProdDevTools: boolean
}
function initProjectFeature({ command }: InitFeaturesOptions) {
const features: ProjectFeatures = {}
if (command === 'build') {
function initProjectFeature({ inputDir }: InitFeaturesOptions) {
const features: ProjectFeatures = {
i18nLocale: false,
i18nEn: true,
i18nEs: true,
i18nFr: true,
i18nZhHans: true,
i18nZhHant: true,
}
const localesDir = path.resolve(inputDir, 'locales')
if (fs.existsSync(localesDir)) {
if (
fs.readdirSync(localesDir).find((file) => path.extname(file) === '.json')
) {
features.i18nLocale = true
}
}
return features
}
......@@ -155,11 +170,6 @@ function initManifestFeature({
promise: false,
longpress: true,
routerMode: '"hash"',
i18nEn: true,
i18nEs: true,
i18nFr: true,
i18nZhHans: true,
i18nZhHant: true,
vueOptionsApi: true,
vueProdDevTools: false,
}
......@@ -176,28 +186,6 @@ function initManifestFeature({
) {
features.routerMode = '"history"'
}
const platformJson = manifestJson[platform] || {}
const manifestFeatures = platformJson.features
if (manifestFeatures) {
const { i18n } = manifestFeatures
if (isArray(i18n)) {
if (!i18n.includes('en')) {
features.i18nEn = false
}
if (!i18n.includes('es')) {
features.i18nEs = false
}
if (!i18n.includes('fr')) {
features.i18nFr = false
}
if (!i18n.includes('zh-Hans')) {
features.i18nZhHans = false
}
if (!i18n.includes('zh-Hant')) {
features.i18nZhHant = false
}
}
}
// TODO other features
return features
}
......@@ -224,6 +212,7 @@ export function initFeatures(options: InitFeaturesOptions) {
i18nFr,
i18nZhHans,
i18nZhHant,
i18nLocale,
vueOptionsApi,
vueProdDevTools,
pages,
......@@ -261,6 +250,7 @@ export function initFeatures(options: InitFeaturesOptions) {
__UNI_FEATURE_I18N_ZH_HANS__: i18nZhHans, // 是否启用zh_Hans
__UNI_FEATURE_I18N_ZH_HANT__: i18nZhHant, // 是否启用zh_Hant
// 以下特性,编译器已自动识别是否需要启用
__UNI_FEATURE_I18N_LOCALE__: i18nLocale, // 是否启用i18n
__UNI_FEATURE_NVUE__: nvue, // 是否启用nvue
__UNI_FEATURE_ROUTER_MODE__: routerMode, // 路由模式
__UNI_FEATURE_PAGES__: pages, // 是否多页面
......
import { Plugin, ResolvedConfig } from 'vite'
import { FileWatcher } from '../../watcher'
import { FileWatcher, FileWatcherOptions } from '../../watcher'
import { M } from '../../messages'
export interface UniViteCopyPluginTarget {
src: string | string[]
dest: string
}
export type UniViteCopyPluginTarget = Omit<FileWatcherOptions, 'verbose'>
export interface UniViteCopyPluginOptions {
targets: UniViteCopyPluginTarget[]
verbose: boolean
......
import fs from 'fs-extra'
import path from 'path'
import { FSWatcher, watch, WatchOptions } from 'chokidar'
interface FileWatcherOptions {
type FileTransform = (source: Buffer, filename: string) => void | string
export interface FileWatcherOptions {
src: string | string[]
dest: string
transform?: FileTransform
verbose?: boolean
}
export class FileWatcher {
private src: string[]
private dest: string
private transform?: FileTransform
private verbose?: boolean
private watcher!: FSWatcher
private onChange?: () => void
constructor({ src, dest, verbose }: FileWatcherOptions) {
constructor({ src, dest, transform, verbose }: FileWatcherOptions) {
this.src = !Array.isArray(src) ? [src] : src
this.dest = dest
this.transform = transform
this.verbose = verbose
}
watch(
......@@ -57,6 +60,19 @@ export class FileWatcher {
copy(from: string) {
const to = this.to(from)
this.info('copy', from + '=>' + to)
let content: string | void
if (this.transform) {
const filename = this.from(from)
content = this.transform(fs.readFileSync(filename), filename)
}
if (content) {
return fs
.outputFile(to, content)
.catch((e) => {
// this.info('copy', e)
})
.then(() => this.onChange && this.onChange())
}
return fs
.copy(this.from(from), to, { overwrite: true })
.catch((e) => {
......
......@@ -77,7 +77,7 @@ export default {
}
},
collection: {
type: String,
type: [String, Array],
default: ''
},
action: {
......@@ -172,6 +172,21 @@ export default {
errorMessage: ''
}
},
computed: {
collectionArgs () {
return Array.isArray(this.collection) ? this.collection : [this.collection]
},
isLookup () {
return (Array.isArray(this.collection) && this.collection.length > 1) || (typeof this.collection === 'string' && this.collection.indexOf(',') > -1)
},
mainCollection () {
if (typeof this.collection === 'string') {
return this.collection.split(',')[0]
}
const mainQuery = JSON.parse(JSON.stringify(this.collection[0]))
return mainQuery.$db[0].$param[0]
}
},
created() {
this._isEnded = false
this.paginationInternal = {
......@@ -319,7 +334,7 @@ export default {
db = db.action(action)
}
db.collection(this._getCollection()).add(value).then((res) => {
db.collection(this.mainCollection).add(value).then((res) => {
success && success(res)
if (showToast) {
uni.showToast({
......@@ -393,7 +408,7 @@ export default {
db = db.action(action)
}
return db.collection(this._getCollection()).doc(id).update(value).then((res) => {
return db.collection(this.mainCollection).doc(id).update(value).then((res) => {
success && success(res)
if (showToast) {
uni.showToast({
......@@ -415,52 +430,7 @@ export default {
complete && complete()
})
},
_execLoadData(callback, clear) {
if (this.loading) {
return
}
this.loading = true
this.errorMessage = ''
return this._getExec().then((res) => {
this.loading = false
const {
data,
count
} = res.result
this._isEnded = count !== undefined ? (this.paginationInternal.current * this.paginationInternal.size >= count) : (data.length < this.pageSize)
this.hasMore = !this._isEnded
const data2 = this.getone ? (data.length ? data[0] : undefined) : data
if (this.getcount) {
this.paginationInternal.count = count
}
callback && callback(data2, this._isEnded, this.paginationInternal)
this._dispatchEvent(events.load, data2)
if (this.getone || this.pageData === pageMode.replace) {
this.dataList = data2
} else {
if (clear) {
this.dataList = data2
} else {
this.dataList.push(...data2)
}
}
}).catch((err) => {
this.loading = false
this.errorMessage = err
callback && callback()
this.$emit(events.error, err)
if (process.env.NODE_ENV === 'development') {
console.error(err)
}
})
},
_getExec() {
getTemp(isTemp = true) {
/* eslint-disable no-undef */
let db = uniCloud.database()
......@@ -468,7 +438,7 @@ export default {
db = db.action(this.action)
}
db = db.collection(this.collection)
db = db.collection(...this.collectionArgs)
if (!(!this.where || !Object.keys(this.where).length)) {
db = db.where(this.where)
......@@ -510,10 +480,77 @@ export default {
if (this.gettreepath) {
getOptions.getTreePath = treeOptions
}
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
db = db.skip(size * (current - 1)).limit(size)
if (isTemp) {
db = db.getTemp(getOptions)
db.udb = this
} else {
db = db.get(getOptions)
}
return db
},
setResult(result) {
if (result.code === 0) {
this._execLoadDataSuccess(result)
} else {
this._execLoadDataFail(new Error(result.message))
}
},
_execLoadData(callback, clear) {
if (this.loading) {
return
}
this.loading = true
this.errorMessage = ''
return this._getExec().then((res) => {
this.loading = false
this._execLoadDataSuccess(res.result, callback, clear)
}).catch((err) => {
this.loading = false
this._execLoadDataFail(err, callback)
})
},
_execLoadDataSuccess(result, callback, clear) {
const {
data,
count
} = result
this._isEnded = count !== undefined ? (this.paginationInternal.current * this.paginationInternal.size >= count) : (data.length < this.pageSize)
this.hasMore = !this._isEnded
const data2 = this.getone ? (data.length ? data[0] : undefined) : data
if (this.getcount) {
this.paginationInternal.count = count
}
callback && callback(data2, this._isEnded, this.paginationInternal)
this._dispatchEvent(events.load, data2)
if (this.getone || this.pageData === pageMode.replace) {
this.dataList = data2
} else {
if (clear) {
this.dataList = data2
} else {
this.dataList.push(...data2)
}
}
},
_execLoadDataFail(err, callback) {
this.errorMessage = err
callback && callback()
this.$emit(events.error, err)
if (process.env.NODE_ENV === 'development') {
console.error(err)
}
},
_getExec() {
return this.getTemp(false)
},
_execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle) {
if (!this.collection || !id) {
return
......@@ -540,7 +577,7 @@ export default {
exec = exec.action(action)
}
exec.collection(this._getCollection()).where({
exec.collection(this.mainCollection).where({
_id: dbCmd.in(ids)
}).remove().then((res) => {
success && success(res.result)
......@@ -564,11 +601,6 @@ export default {
complete && complete()
})
},
_getCollection() {
const index = this.collection.indexOf(',')
const collection = index > 0 ? this.collection.substring(0, index) : this.collection
return collection
},
removeData(ids) {
const il = ids.slice(0)
const dl = this.dataList
......
import { getEnvLocale } from '@dcloudio/uni-shared'
import { BuiltInLocale, initVueI18n } from '@dcloudio/uni-i18n'
import { getEnvLocale, I18N_JSON_DELIMITERS } from '@dcloudio/uni-shared'
import { BuiltInLocale, initVueI18n, isI18nStr } from '@dcloudio/uni-i18n'
let i18n: ReturnType<typeof initVueI18n>
......@@ -7,6 +7,21 @@ interface webviewStyleWithLanguage extends PlusWebviewWebviewStyles {
locale: string
}
function getLocaleMessage() {
const locale = useI18n().getLocale()
const locales = __uniConfig.locales
return (
locales[locale] || locales[__uniConfig.fallbackLocale] || locales.en || {}
)
}
export function formatI18n(message: string) {
if (__uniConfig.locales && isI18nStr(message, I18N_JSON_DELIMITERS)) {
return useI18n().f(message, getLocaleMessage())
}
return message
}
export function useI18n() {
if (!i18n) {
let locale: BuiltInLocale
......
......@@ -4,13 +4,12 @@ import { ModuleGraph, Plugin } from 'vite'
import { extend } from '@vue/shared'
import {
initEasycomsOnce,
initFeatures,
normalizePath,
parseManifestJson,
parsePagesJson,
} from '@dcloudio/uni-cli-shared'
import { initFeatures } from '../../utils'
const debugHmr = debug('vite:uni:hmr')
async function invalidate(file: string, moduleGraph: ModuleGraph) {
......
......@@ -37,10 +37,10 @@ export function uniMainJsPlugin() {
}
function createApp(code: string) {
return `import { plugin as __plugin } from '@dcloudio/uni-h5';createApp().app.use(__plugin).mount("#app");${code.replace(
return `import { plugin as __plugin } from '@dcloudio/uni-h5';${code.replace(
'createSSRApp',
'createVueApp as createSSRApp'
)}`
)};createApp().app.use(__plugin).mount("#app");`
}
function createLegacyApp(code: string) {
......@@ -51,7 +51,7 @@ function createLegacyApp(code: string) {
}
function createSSRClientApp(code: string) {
return `import { plugin as __plugin } from '@dcloudio/uni-h5';import { UNI_SSR, UNI_SSR_STORE } from '@dcloudio/uni-shared';const { app: __app, store: __store } = createApp();__app.use(__plugin);__store && window[UNI_SSR] && window[UNI_SSR][UNI_SSR_STORE] && __store.replaceState(window[UNI_SSR][UNI_SSR_STORE]);__app.router.isReady().then(() => __app.mount("#app"));${code}`
return `import { plugin as __plugin } from '@dcloudio/uni-h5';import { UNI_SSR, UNI_SSR_STORE } from '@dcloudio/uni-shared';${code};const { app: __app, store: __store } = createApp();__app.use(__plugin);__store && window[UNI_SSR] && window[UNI_SSR][UNI_SSR_STORE] && __store.replaceState(window[UNI_SSR][UNI_SSR_STORE]);__app.router.isReady().then(() => __app.mount("#app"));`
}
function createSSRServerApp(code: string) {
......
......@@ -54,8 +54,9 @@ function generatePagesJsonCode(
return `
import { defineAsyncComponent, resolveComponent, createVNode, withCtx, openBlock, createBlock } from 'vue'
import { PageComponent, AsyncLoadingComponent, AsyncErrorComponent, setupWindow } from '@dcloudio/uni-h5'
import { PageComponent, AsyncLoadingComponent, AsyncErrorComponent, useI18n, setupWindow } from '@dcloudio/uni-h5'
import { appid, debug, networkTimeout, router, async, sdkConfigs, qqMapKey, nvue, locale } from '${manifestJsonPath}'
const locales = import.meta.globEager('./locale/*.json')
${importLayoutComponentsCode}
const extend = Object.assign
${cssCode}
......@@ -64,6 +65,11 @@ ${defineLayoutComponentsCode}
${definePagesCode}
${uniRoutesCode}
${config.command === 'serve' ? hmrCode : ''}
const localeKeys = Object.keys(__uniConfig.locales)
if (localeKeys.length) {
const i18n = useI18n()
localeKeys.forEach(locale=>i18n.add(locale,__uniConfig.locales[locale]))
}
export {}
`
}
......@@ -260,7 +266,8 @@ delete ${globalName}['____'+appid+'____']
qqMapKey,
nvue,
locale,
router
locales:Object.keys(locales).reduce((res,name)=>{res[name.replace(/\\.\\/locale\\/(.*).json/,'$1')]=locales[name].default;return res},{}),
router,
})
`
)
......
import fs from 'fs'
import path from 'path'
import { ConfigEnv, UserConfig } from 'vite'
import { extend, isArray, isString } from '@vue/shared'
import {
parsePagesJsonOnce,
parseManifestJsonOnce,
initFeatures,
} from '@dcloudio/uni-cli-shared'
import { isSsr, isSsrManifest } from './ssr'
interface ProjectFeatures {}
interface PagesFeatures {
nvue: boolean
pages: boolean
tabBar: boolean
tabBarMidButton: boolean
topWindow: boolean
leftWindow: boolean
rightWindow: boolean
navigationBar: boolean
pullDownRefresh: boolean
navigationBarButtons: boolean
navigationBarSearchInput: boolean
navigationBarTransparent: boolean
}
interface ManifestFeatures {
wx: boolean
wxs: boolean
rpx: boolean
promise: boolean
longpress: boolean
routerMode: '"hash"' | '"history"'
i18nEn: boolean
i18nEs: boolean
i18nFr: boolean
i18nZhHans: boolean
i18nZhHant: boolean
vueOptionsApi: boolean
vueProdDevTools: boolean
}
function initProjectFeature({ command }: InitFeaturesOptions) {
const features: ProjectFeatures = {}
if (command === 'build') {
}
return features
}
function initPagesFeature({
pagesJson,
command,
inputDir,
ssr,
}: InitFeaturesOptions): PagesFeatures {
const features: PagesFeatures = {
nvue: true,
pages: true,
tabBar: true,
tabBarMidButton: true,
topWindow: false,
leftWindow: false,
rightWindow: false,
navigationBar: true,
pullDownRefresh: false,
navigationBarButtons: true,
navigationBarSearchInput: true,
navigationBarTransparent: true,
}
const { tabBar, pages, topWindow, leftWindow, rightWindow, globalStyle } =
pagesJson
// ssr 时强制启用多页面(需要用到router)
if (!ssr && pages && pages.length === 1) {
features.pages = false
}
if (!(tabBar && tabBar.list && tabBar.list.length)) {
features.tabBar = false
features.tabBarMidButton = false
}
if (features.tabBar && !tabBar!.midButton) {
features.tabBarMidButton = false
}
if (topWindow && topWindow.path) {
features.topWindow = true
}
if (leftWindow && leftWindow.path) {
features.leftWindow = true
}
if (rightWindow && rightWindow.path) {
features.rightWindow = true
}
if (globalStyle.enablePullDownRefresh) {
features.pullDownRefresh = true
} else {
if (pages.find((page) => page.style && page.style.enablePullDownRefresh)) {
features.pullDownRefresh = true
}
}
if (command === 'build') {
if (
!pages.find((page) =>
fs.existsSync(path.resolve(inputDir, page.path + '.nvue'))
)
) {
features.nvue = false
}
let isNavigationCustom = false
if (globalStyle.navigationBar.style === 'custom') {
isNavigationCustom = true // 全局custom
if (pages.find((page) => page.style.navigationBar.style === 'default')) {
isNavigationCustom = false
}
} else {
// 所有页面均custom
if (pages.every((page) => page.style.navigationBar.style === 'custom')) {
isNavigationCustom = true
}
}
if (isNavigationCustom) {
features.navigationBar = false
features.navigationBarButtons = false
features.navigationBarSearchInput = false
features.navigationBarTransparent = false
} else {
if (
!pages.find(
(page) =>
isArray(page.style.navigationBar.buttons) &&
page.style.navigationBar.buttons.length
)
) {
features.navigationBarButtons = false
}
if (
!globalStyle.navigationBar.searchInput &&
!pages.find((page) => page.style.navigationBar.searchInput)
) {
features.navigationBarSearchInput = false
}
if (
globalStyle.navigationBar.type !== 'transparent' &&
!pages.find((page) => page.style.navigationBar.type === 'transparent')
) {
features.navigationBarTransparent = false
}
}
}
return features
}
function initManifestFeature({
manifestJson,
command,
platform,
}: InitFeaturesOptions): ManifestFeatures {
const features: ManifestFeatures = {
wx: false,
wxs: true,
rpx: true,
promise: false,
longpress: true,
routerMode: '"hash"',
i18nEn: true,
i18nEs: true,
i18nFr: true,
i18nZhHans: true,
i18nZhHant: true,
vueOptionsApi: true,
vueProdDevTools: false,
}
if (command === 'build') {
// TODO 需要预编译一遍?
features.wxs = true
features.longpress = true
}
if (
manifestJson.h5 &&
manifestJson.h5.router &&
manifestJson.h5.router.mode === 'history'
) {
features.routerMode = '"history"'
}
const platformJson = manifestJson[platform] || {}
const manifestFeatures = platformJson.features
if (manifestFeatures) {
const { i18n } = manifestFeatures
if (isArray(i18n)) {
if (!i18n.includes('en')) {
features.i18nEn = false
}
if (!i18n.includes('es')) {
features.i18nEs = false
}
if (!i18n.includes('fr')) {
features.i18nFr = false
}
if (!i18n.includes('zh-Hans')) {
features.i18nZhHans = false
}
if (!i18n.includes('zh-Hant')) {
features.i18nZhHant = false
}
}
}
// TODO other features
return features
}
export type FEATURE_DEFINES = ReturnType<typeof initFeatures>
interface InitFeaturesOptions {
pagesJson: UniApp.PagesJson
manifestJson: any
inputDir: string
platform: UniApp.PLATFORM
command: ConfigEnv['command']
ssr: boolean
}
export function initFeatures(options: InitFeaturesOptions) {
const {
wx,
wxs,
rpx,
nvue,
i18nEn,
i18nEs,
i18nFr,
i18nZhHans,
i18nZhHant,
vueOptionsApi,
vueProdDevTools,
pages,
tabBar,
tabBarMidButton,
promise,
longpress,
routerMode,
topWindow,
leftWindow,
rightWindow,
navigationBar,
pullDownRefresh,
navigationBarButtons,
navigationBarSearchInput,
navigationBarTransparent,
} = extend(
initManifestFeature(options),
initPagesFeature(options),
initProjectFeature(options)
)
const features = {
// vue
__VUE_OPTIONS_API__: vueOptionsApi, // enable/disable Options API support, default: true
__VUE_PROD_DEVTOOLS__: vueProdDevTools, // enable/disable devtools support in production, default: false
// uni
__UNI_FEATURE_WX__: wx, // 是否启用小程序的组件实例 API,如:selectComponent 等(uni-core/src/service/plugin/appConfig)
__UNI_FEATURE_WXS__: wxs, // 是否启用 wxs 支持,如:getComponentDescriptor 等(uni-core/src/view/plugin/appConfig)
__UNI_FEATURE_RPX__: rpx, // 是否启用运行时 rpx 支持
__UNI_FEATURE_PROMISE__: promise, // 是否启用旧版本的 promise 支持(即返回[err,res]的格式),默认返回标准
__UNI_FEATURE_LONGPRESS__: longpress, // 是否启用longpress
__UNI_FEATURE_I18N_EN__: i18nEn, // 是否启用en
__UNI_FEATURE_I18N_ES__: i18nEs, // 是否启用es
__UNI_FEATURE_I18N_FR__: i18nFr, // 是否启用fr
__UNI_FEATURE_I18N_ZH_HANS__: i18nZhHans, // 是否启用zh_Hans
__UNI_FEATURE_I18N_ZH_HANT__: i18nZhHant, // 是否启用zh_Hant
// 以下特性,编译器已自动识别是否需要启用
__UNI_FEATURE_NVUE__: nvue, // 是否启用nvue
__UNI_FEATURE_ROUTER_MODE__: routerMode, // 路由模式
__UNI_FEATURE_PAGES__: pages, // 是否多页面
__UNI_FEATURE_TABBAR__: tabBar, // 是否包含tabBar
__UNI_FEATURE_TABBAR_MIDBUTTON__: tabBarMidButton, // 是否包含midButton
__UNI_FEATURE_TOPWINDOW__: topWindow, // 是否包含topWindow
__UNI_FEATURE_LEFTWINDOW__: leftWindow, // 是否包含leftWindow
__UNI_FEATURE_RIGHTWINDOW__: rightWindow, // 是否包含rightWindow
__UNI_FEATURE_RESPONSIVE__: topWindow || leftWindow || rightWindow, // 是否启用响应式
__UNI_FEATURE_NAVIGATIONBAR__: navigationBar, // 是否启用标题栏
__UNI_FEATURE_PULL_DOWN_REFRESH__: pullDownRefresh, // 是否启用下拉刷新
__UNI_FEATURE_NAVIGATIONBAR_BUTTONS__: navigationBarButtons, // 是否启用标题栏按钮
__UNI_FEATURE_NAVIGATIONBAR_SEARCHINPUT__: navigationBarSearchInput, // 是否启用标题栏搜索框
__UNI_FEATURE_NAVIGATIONBAR_TRANSPARENT__: navigationBarTransparent, // 是否启用透明标题栏
}
// ssr nodejs features
if (options.ssr) {
Object.keys(features).forEach((name) => {
const value = features[name as keyof typeof features]
extend(globalThis, {
[name]: isString(value) ? JSON.parse(value) : value,
})
})
}
return features
}
export function createDefine(
command: ConfigEnv['command'],
config: UserConfig
......
......@@ -26,6 +26,7 @@ const BLACKLIST = [
'cssConstant',
'cssEnv',
'cssVar',
'useI18n',
]
export function genApiJson(code: string) {
......
......@@ -7,6 +7,17 @@ var uniI18n = require("@dcloudio/uni-i18n");
var shared = require("@vue/shared");
var vueRouter = require("vue-router");
let i18n;
function getLocaleMessage() {
const locale = useI18n().getLocale();
const locales = __uniConfig.locales;
return locales[locale] || locales[__uniConfig.fallbackLocale] || locales.en || {};
}
function formatI18n(message) {
if (__uniConfig.locales && uniI18n.isI18nStr(message, uniShared.I18N_JSON_DELIMITERS)) {
return useI18n().f(message, getLocaleMessage());
}
return message;
}
function useI18n() {
if (!i18n) {
let locale;
......@@ -21,70 +32,73 @@ function useI18n() {
}
return i18n;
}
function normalizeMessages(namespace, messages) {
return Object.keys(messages).reduce((res, name) => {
res[namespace + name] = messages[name];
function normalizeMessages(module, keys, values) {
return keys.reduce((res, name, index2) => {
res[module + name] = values[index2];
return res;
}, {});
}
const initI18nAsyncMsgsOnce = /* @__PURE__ */ uniShared.once(() => {
const name = "uni.async.";
const keys = ["error"];
if (__UNI_FEATURE_I18N_EN__) {
useI18n().add(uniI18n.LOCALE_EN, normalizeMessages(name, {
error: "The connection timed out, click the screen to try again."
}));
useI18n().add(uniI18n.LOCALE_EN, normalizeMessages(name, keys, [
"The connection timed out, click the screen to try again."
]), false);
}
if (__UNI_FEATURE_I18N_ES__) {
useI18n().add(uniI18n.LOCALE_ES, normalizeMessages(name, {
error: "Se agot\xF3 el tiempo de conexi\xF3n, haga clic en la pantalla para volver a intentarlo."
}));
useI18n().add(uniI18n.LOCALE_ES, normalizeMessages(name, keys, [
"Se agot\xF3 el tiempo de conexi\xF3n, haga clic en la pantalla para volver a intentarlo."
]), false);
}
if (__UNI_FEATURE_I18N_FR__) {
useI18n().add(uniI18n.LOCALE_FR, normalizeMessages(name, {
error: "La connexion a expir\xE9, cliquez sur l'\xE9cran pour r\xE9essayer."
}));
useI18n().add(uniI18n.LOCALE_FR, normalizeMessages(name, keys, [
"La connexion a expir\xE9, cliquez sur l'\xE9cran pour r\xE9essayer."
]), false);
}
if (__UNI_FEATURE_I18N_ZH_HANS__) {
useI18n().add(uniI18n.LOCALE_ZH_HANS, normalizeMessages(name, { error: "\u8FDE\u63A5\u670D\u52A1\u5668\u8D85\u65F6\uFF0C\u70B9\u51FB\u5C4F\u5E55\u91CD\u8BD5" }));
useI18n().add(uniI18n.LOCALE_ZH_HANS, normalizeMessages(name, keys, ["\u8FDE\u63A5\u670D\u52A1\u5668\u8D85\u65F6\uFF0C\u70B9\u51FB\u5C4F\u5E55\u91CD\u8BD5"]), false);
}
if (__UNI_FEATURE_I18N_ZH_HANT__) {
useI18n().add(uniI18n.LOCALE_ZH_HANT, normalizeMessages(name, { error: "\u9023\u63A5\u670D\u52D9\u5668\u8D85\u6642\uFF0C\u9EDE\u64CA\u5C4F\u5E55\u91CD\u8A66" }));
useI18n().add(uniI18n.LOCALE_ZH_HANT, normalizeMessages(name, keys, ["\u9023\u63A5\u670D\u52D9\u5668\u8D85\u6642\uFF0C\u9EDE\u64CA\u5C4F\u5E55\u91CD\u8A66"]), false);
}
});
const initI18nPickerMsgsOnce = /* @__PURE__ */ uniShared.once(() => {
const name = "uni.picker.";
const keys = ["done", "cancel"];
if (__UNI_FEATURE_I18N_EN__) {
useI18n().add(uniI18n.LOCALE_EN, normalizeMessages(name, { done: "Done", cancel: "Cancel" }));
useI18n().add(uniI18n.LOCALE_EN, normalizeMessages(name, keys, ["Done", "Cancel"]), false);
}
if (__UNI_FEATURE_I18N_ES__) {
useI18n().add(uniI18n.LOCALE_ES, normalizeMessages(name, { done: "OK", cancel: "Cancelar" }));
useI18n().add(uniI18n.LOCALE_ES, normalizeMessages(name, keys, ["OK", "Cancelar"]), false);
}
if (__UNI_FEATURE_I18N_FR__) {
useI18n().add(uniI18n.LOCALE_FR, normalizeMessages(name, { done: "OK", cancel: "Annuler" }));
useI18n().add(uniI18n.LOCALE_FR, normalizeMessages(name, keys, ["OK", "Annuler"]), false);
}
if (__UNI_FEATURE_I18N_ZH_HANS__) {
useI18n().add(uniI18n.LOCALE_ZH_HANS, normalizeMessages(name, { done: "\u5B8C\u6210", cancel: "\u53D6\u6D88" }));
useI18n().add(uniI18n.LOCALE_ZH_HANS, normalizeMessages(name, keys, ["\u5B8C\u6210", "\u53D6\u6D88"]), false);
}
if (__UNI_FEATURE_I18N_ZH_HANT__) {
useI18n().add(uniI18n.LOCALE_ZH_HANT, normalizeMessages(name, { done: "\u5B8C\u6210", cancel: "\u53D6\u6D88" }));
useI18n().add(uniI18n.LOCALE_ZH_HANT, normalizeMessages(name, keys, ["\u5B8C\u6210", "\u53D6\u6D88"]), false);
}
});
const initI18nVideoMsgsOnce = /* @__PURE__ */ uniShared.once(() => {
const name = "uni.video.";
const keys = ["danmu", "volume"];
if (__UNI_FEATURE_I18N_EN__) {
useI18n().add(uniI18n.LOCALE_EN, normalizeMessages(name, { danmu: "Danmu", volume: "Volume" }));
useI18n().add(uniI18n.LOCALE_EN, normalizeMessages(name, keys, ["Danmu", "Volume"]), false);
}
if (__UNI_FEATURE_I18N_ES__) {
useI18n().add(uniI18n.LOCALE_ES, normalizeMessages(name, { danmu: "Danmu", volume: "Volumen" }));
useI18n().add(uniI18n.LOCALE_ES, normalizeMessages(name, keys, ["Danmu", "Volumen"]), false);
}
if (__UNI_FEATURE_I18N_FR__) {
useI18n().add(uniI18n.LOCALE_FR, normalizeMessages(name, { danmu: "Danmu", volume: "Le Volume" }));
useI18n().add(uniI18n.LOCALE_FR, normalizeMessages(name, keys, ["Danmu", "Le Volume"]), false);
}
if (__UNI_FEATURE_I18N_ZH_HANS__) {
useI18n().add(uniI18n.LOCALE_ZH_HANS, normalizeMessages(name, { danmu: "\u5F39\u5E55", volume: "\u97F3\u91CF" }));
useI18n().add(uniI18n.LOCALE_ZH_HANS, normalizeMessages(name, keys, ["\u5F39\u5E55", "\u97F3\u91CF"]), false);
}
if (__UNI_FEATURE_I18N_ZH_HANT__) {
useI18n().add(uniI18n.LOCALE_ZH_HANT, normalizeMessages(name, { danmu: "\u5F48\u5E55", volume: "\u97F3\u91CF" }));
useI18n().add(uniI18n.LOCALE_ZH_HANT, normalizeMessages(name, keys, ["\u5F48\u5E55", "\u97F3\u91CF"]), false);
}
});
const E = function() {
......@@ -10329,6 +10343,11 @@ function createPageHeadTitleTextTsx({
titleText,
titleImage
}) {
if (__UNI_FEATURE_I18N_LOCALE__) {
if (!titleImage && titleText) {
titleText = formatI18n(titleText);
}
}
return vue.createVNode("div", {
"class": "uni-page-head-bd"
}, [vue.createVNode("div", {
......@@ -10765,4 +10784,5 @@ exports.setupApp = setupApp;
exports.setupPage = setupPage;
exports.setupWindow = setupWindow;
exports.uni = uni$1;
exports.useI18n = useI18n;
exports.useTabBar = useTabBar;
此差异已折叠。
......@@ -9,6 +9,7 @@ import {
ICON_PATH_SEARCH,
ICON_PATH_BACK,
ICON_PATH_CLOSE,
formatI18n,
} from '@dcloudio/uni-core'
import { usePageMeta } from '../../setup/provide'
import {
......@@ -157,6 +158,11 @@ function createPageHeadTitleTextTsx({
titleText,
titleImage,
}: UniApp.PageNavigationBar) {
if (__UNI_FEATURE_I18N_LOCALE__) {
if (!titleImage && titleText) {
titleText = formatI18n(titleText)
}
}
return (
<div class="uni-page-head-bd">
<div
......@@ -315,7 +321,7 @@ function usePageHeadButtons({ id, navigationBar }: UniApp.PageRouteMeta) {
}
btn.fontFamily = fontFamily
}
const pageHeadBtn = usePageHeadButton(id, index, btn, isTransparent)
const pageHeadBtn = usePageHeadButton(id!, index, btn, isTransparent)
if (btn.float === 'left') {
left.push(pageHeadBtn)
} else {
......@@ -374,7 +380,7 @@ function usePageHeadSearchInput({
const { disabled } = searchInput!
if (disabled) {
const onClick = () => {
invokeHook(id, 'onNavigationBarSearchInputClicked')
invokeHook(id!, 'onNavigationBarSearchInputClicked')
}
return {
focus,
......@@ -385,19 +391,19 @@ function usePageHeadSearchInput({
}
const onFocus = () => {
focus.value = true
invokeHook(id, 'onNavigationBarSearchInputFocusChanged', { focus: true })
invokeHook(id!, 'onNavigationBarSearchInputFocusChanged', { focus: true })
}
const onBlur = () => {
focus.value = false
invokeHook(id, 'onNavigationBarSearchInputFocusChanged', { focus: false })
invokeHook(id!, 'onNavigationBarSearchInputFocusChanged', { focus: false })
}
const onInput = (evt: { detail: { value: string } }) => {
text.value = evt.detail.value
invokeHook(id, 'onNavigationBarSearchInputChanged', { text: text.value })
invokeHook(id!, 'onNavigationBarSearchInputChanged', { text: text.value })
}
const onKeyup = (evt: KeyboardEvent) => {
if (evt.key === 'Enter' || evt.keyCode === 13) {
invokeHook(id, 'onNavigationBarSearchInputConfirmed', {
invokeHook(id!, 'onNavigationBarSearchInputConfirmed', {
text: text.value,
})
}
......
......@@ -29,6 +29,8 @@ export {
View,
} from '@dcloudio/uni-components'
export { useI18n } from '@dcloudio/uni-core'
export { default as plugin } from './framework/plugin'
export * from './framework/setup'
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`compileI18nJsonStr androidPrivacy.json 1`] = `
"{
\\"version\\": \\"1\\",
\\"prompt\\": \\"template\\",
\\"title\\": \\"服务协议和隐私政策\\",
\\"message\\": \\"  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\\\\\\"\\\\\\">《服务协议》</a>和<a href=\\\\\\"\\\\\\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。\\",
\\"buttonAccept\\": \\"同意并接受\\",
\\"buttonRefuse\\": \\"暂不同意\\",
\\"second\\": {
\\"title\\": \\"确认提示\\",
\\"message\\": \\"  进入应用前,你需先同意<a href=\\\\\\"\\\\\\">《服务协议》</a>和<a href=\\\\\\"\\\\\\">《隐私政策》</a>,否则将退出应用。\\",
\\"buttonAccept\\": \\"同意并继续\\",
\\"buttonRefuse\\": \\"退出应用\\",
\\"titleLocales\\": {
\\"zh-Hans\\": \\"确认提示\\",
\\"en\\": \\"confirm\\"
}
},
\\"styles\\": {
\\"backgroundColor\\": \\"#00FF00\\",
\\"borderRadius\\": \\"5px\\",
\\"title\\": {
\\"color\\": \\"#ff00ff\\",
\\"colorLocales\\": {
\\"zh-Hans\\": \\"#ff00ff\\",
\\"en\\": \\"#ff00ff\\"
}
},
\\"buttonAccept\\": {
\\"color\\": \\"#ffff00\\"
},
\\"buttonRefuse\\": {
\\"color\\": \\"#00ffff\\"
}
},
\\"titleLocales\\": {
\\"zh-Hans\\": \\"服务协议和隐私政策\\",
\\"en\\": \\"Privacy\\"
},
\\"messageLocales\\": {
\\"zh-Hans\\": \\"  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\\\\\\"\\\\\\">《服务协议》</a>和<a href=\\\\\\"\\\\\\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。\\",
\\"en\\": \\"  privacy\\"
},
\\"buttonAcceptLocales\\": {
\\"zh-Hans\\": \\"同意并接受\\",
\\"en\\": \\"accept\\"
},
\\"buttonRefuseLocales\\": {
\\"zh-Hans\\": \\"暂不同意\\",
\\"en\\": \\"refuse\\"
}
}"
`;
exports[`compileI18nJsonStr pages.json->tabBar 1`] = `
"{
\\"color\\": \\"#7A7E83\\",
\\"selectedColor\\": \\"#007AFF\\",
\\"borderStyle\\": \\"black\\",
\\"backgroundColor\\": \\"#f8f8f8\\",
\\"list\\": [
{
\\"pagePath\\": \\"pages/tabBar/component/component\\",
\\"iconPath\\": \\"static/component.png\\",
\\"selectedIconPath\\": \\"static/componentHL.png\\",
\\"text\\": \\"组件\\",
\\"textLocales\\": {
\\"zh-Hans\\": \\"组件\\",
\\"en\\": \\"Component\\"
}
},
{
\\"pagePath\\": \\"pages/tabBar/API/API\\",
\\"iconPath\\": \\"static/api.png\\",
\\"selectedIconPath\\": \\"static/apiHL.png\\",
\\"text\\": \\"接口\\",
\\"textLocales\\": {
\\"zh-Hans\\": \\"接口\\",
\\"en\\": \\"API\\"
}
}
],
\\"backgroundColorLocales\\": {
\\"zh-Hans\\": \\"#f8f8f8\\",
\\"en\\": \\"#f6f6f6\\"
}
}"
`;
import { I18N_JSON_DELIMITERS } from '@dcloudio/uni-shared'
import { parseI18nJson, compileI18nJsonStr } from '../src/json'
const delimiters: [string, string] = I18N_JSON_DELIMITERS
describe('parseI18nJson', () => {
test('pages.json->style', () => {
const pageMeta = parseI18nJson(
{
tabBarIndex: 0,
bounce: 'vertical',
navigationBar: {
titleText: '%component.title%组件',
buttons: [
{
text: '%component.btns.0.text%',
fontSrc: '/static/uni.ttf',
fontSize: '22px',
color: '#FFFFFF',
},
],
},
route: 'pages/tabBar/component/component',
},
{
'component.title': '内置',
'component.btns.0.text': '\ue534',
},
delimiters
) as Record<string, any>
expect(pageMeta.navigationBar.titleText).toBe('内置组件')
expect(pageMeta.navigationBar.buttons[0].text).toBe('\ue534')
})
})
describe('compileI18nJsonStr', () => {
test('empty', () => {
expect(
compileI18nJsonStr(JSON.stringify({}), {
locale: '',
locales: {},
delimiters,
})
).toBe('{}')
})
test('pages.json->tabBar', () => {
expect(
compileI18nJsonStr(
JSON.stringify({
color: '#7A7E83',
selectedColor: '#007AFF',
borderStyle: 'black',
backgroundColor: '#%tabBar.backgroundColor%',
list: [
{
pagePath: 'pages/tabBar/component/component',
iconPath: 'static/component.png',
selectedIconPath: 'static/componentHL.png',
text: '%tabBar.0.title%',
},
{
pagePath: 'pages/tabBar/API/API',
iconPath: 'static/api.png',
selectedIconPath: 'static/apiHL.png',
text: '%tabBar.1.title%',
},
],
}),
{
locale: 'zh-Hans',
locales: {
'zh-Hans': {
'tabBar.backgroundColor': 'f8f8f8',
'tabBar.0.title': '组件',
'tabBar.1.title': '接口',
},
en: {
'tabBar.backgroundColor': 'f6f6f6',
'tabBar.0.title': 'Component',
'tabBar.1.title': 'API',
},
},
delimiters,
}
)
).toMatchSnapshot()
})
test('androidPrivacy.json', () => {
expect(
compileI18nJsonStr(
JSON.stringify({
version: '1',
prompt: 'template',
title: '%p.title%',
message: '  %p.message%',
buttonAccept: '%p.accept%',
buttonRefuse: '%p.refuse%',
second: {
title: '%p.second.title%',
message:
'  进入应用前,你需先同意<a href="">《服务协议》</a>和<a href="">《隐私政策》</a>,否则将退出应用。',
buttonAccept: '同意并继续',
buttonRefuse: '退出应用',
},
styles: {
backgroundColor: '#00FF00',
borderRadius: '5px',
title: {
color: '%p.title.color%',
},
buttonAccept: {
color: '#ffff00',
},
buttonRefuse: {
color: '#00ffff',
},
},
}),
{
locale: 'zh-Hans',
locales: {
'zh-Hans': {
'p.title': '服务协议和隐私政策',
'p.message':
'请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href="">《服务协议》</a>和<a href="">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。',
'p.accept': '同意并接受',
'p.refuse': '暂不同意',
'p.second.title': '确认提示',
'p.title.color': '#ff00ff',
},
en: {
'p.title': 'Privacy',
'p.message': 'privacy',
'p.accept': 'accept',
'p.refuse': 'refuse',
'p.second.title': 'confirm',
'p.title.color': '#ff00ff',
},
},
delimiters,
}
)
).toMatchSnapshot()
})
})
......@@ -2,18 +2,20 @@
Object.defineProperty(exports, '__esModule', { value: true });
const isArray = Array.isArray;
const isObject = (val) => val !== null && typeof val === 'object';
const defaultDelimiters = ['{', '}'];
class BaseFormatter {
constructor() {
this._caches = Object.create(null);
}
interpolate(message, values) {
interpolate(message, values, delimiters = defaultDelimiters) {
if (!values) {
return [message];
}
let tokens = this._caches[message];
if (!tokens) {
tokens = parse(message);
tokens = parse(message, delimiters);
this._caches[message] = tokens;
}
return compile(tokens, values);
......@@ -21,24 +23,24 @@ class BaseFormatter {
}
const RE_TOKEN_LIST_VALUE = /^(?:\d)+/;
const RE_TOKEN_NAMED_VALUE = /^(?:\w)+/;
function parse(format) {
function parse(format, [startDelimiter, endDelimiter]) {
const tokens = [];
let position = 0;
let text = '';
while (position < format.length) {
let char = format[position++];
if (char === '{') {
if (char === startDelimiter) {
if (text) {
tokens.push({ type: 'text', value: text });
}
text = '';
let sub = '';
char = format[position++];
while (char !== undefined && char !== '}') {
while (char !== undefined && char !== endDelimiter) {
sub += char;
char = format[position++];
}
const isClosed = char === '}';
const isClosed = char === endDelimiter;
const type = RE_TOKEN_LIST_VALUE.test(sub)
? 'list'
: isClosed && RE_TOKEN_NAMED_VALUE.test(sub)
......@@ -46,12 +48,12 @@ function parse(format) {
: 'unknown';
tokens.push({ value: sub, type });
}
else if (char === '%') {
// when found rails i18n syntax, skip text capture
if (format[position] !== '{') {
text += char;
}
}
// else if (char === '%') {
// // when found rails i18n syntax, skip text capture
// if (format[position] !== '{') {
// text += char
// }
// }
else {
text += char;
}
......@@ -62,7 +64,7 @@ function parse(format) {
function compile(tokens, values) {
const compiled = [];
let index = 0;
const mode = Array.isArray(values)
const mode = isArray(values)
? 'list'
: isObject(values)
? 'named'
......@@ -165,9 +167,12 @@ class I18n {
this.messages[this.locale] = {};
}
this.message = this.messages[this.locale];
this.watchers.forEach((watcher) => {
watcher(this.locale, oldLocale);
});
// 仅发生变化时,通知
if (oldLocale !== this.locale) {
this.watchers.forEach((watcher) => {
watcher(this.locale, oldLocale);
});
}
}
getLocale() {
return this.locale;
......@@ -178,14 +183,27 @@ class I18n {
this.watchers.splice(index, 1);
};
}
add(locale, message) {
if (this.messages[locale]) {
Object.assign(this.messages[locale], message);
add(locale, message, override = true) {
const curMessages = this.messages[locale];
if (curMessages) {
if (override) {
Object.assign(curMessages, message);
}
else {
Object.keys(message).forEach((key) => {
if (!hasOwn(curMessages, key)) {
curMessages[key] = message[key];
}
});
}
}
else {
this.messages[locale] = message;
}
}
f(message, values) {
return this.formater.interpolate(message, values).join('');
}
t(key, locale, values) {
let message = this.message;
if (typeof locale === 'string') {
......@@ -224,6 +242,7 @@ function initLocaleWatcher(appVm, i18n) {
// }
// return uni.getSystemInfoSync().language
// }
const i18nInstances = [];
function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_EN, watcher) {
// 兼容旧版本入参
if (typeof locale !== 'string') {
......@@ -238,6 +257,7 @@ function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_
messages,
watcher,
});
i18nInstances.push(i18n);
let t = (key, values) => {
if (typeof getApp !== 'function') {
// app view
......@@ -277,21 +297,135 @@ function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_
};
return {
i18n,
f(message, values) {
return i18n.f(message, values);
},
t(key, values) {
return t(key, values);
},
add(locale, message) {
return i18n.add(locale, message);
add(locale, message, override = true) {
return i18n.add(locale, message, override);
},
watch(fn) {
return i18n.watchLocale(fn);
},
getLocale() {
return i18n.getLocale();
},
setLocale(newLocale) {
return i18n.setLocale(newLocale);
// 更新所有实例 locale
i18nInstances.forEach((ins) => {
ins.setLocale(newLocale);
});
},
};
}
const isString = (val) => typeof val === 'string';
let formater;
function hasI18nJson(jsonObj, delimiters) {
if (!formater) {
formater = new BaseFormatter();
}
return walkJsonObj(jsonObj, (jsonObj, key) => {
const value = jsonObj[key];
if (isString(value)) {
if (isI18nStr(value, delimiters)) {
return true;
}
}
else {
return hasI18nJson(value, delimiters);
}
});
}
function parseI18nJson(jsonObj, values, delimiters) {
if (!formater) {
formater = new BaseFormatter();
}
walkJsonObj(jsonObj, (jsonObj, key) => {
const value = jsonObj[key];
if (isString(value)) {
if (isI18nStr(value, delimiters)) {
jsonObj[key] = compileStr(value, values, delimiters);
}
}
else {
parseI18nJson(value, values, delimiters);
}
});
return jsonObj;
}
function compileI18nJsonStr(jsonStr, { locale, locales, delimiters, }) {
if (!isI18nStr(jsonStr, delimiters)) {
return jsonStr;
}
if (!formater) {
formater = new BaseFormatter();
}
const localeValues = [];
Object.keys(locales).forEach((name) => {
if (name !== locale) {
localeValues.push({
locale: name,
values: locales[name],
});
}
});
localeValues.unshift({ locale, values: locales[locale] });
try {
return JSON.stringify(compileJsonObj(JSON.parse(jsonStr), localeValues, delimiters), null, 2);
}
catch (e) { }
return jsonStr;
}
function isI18nStr(value, delimiters) {
return value.indexOf(delimiters[0]) > -1;
}
function compileStr(value, values, delimiters) {
return formater.interpolate(value, values, delimiters).join('');
}
function compileValue(jsonObj, key, localeValues, delimiters) {
const value = jsonObj[key];
if (isString(value)) {
// 存在国际化
if (isI18nStr(value, delimiters)) {
jsonObj[key] = compileStr(value, localeValues[0].values, delimiters);
// 格式化国际化语言
const valueLocales = (jsonObj[key + 'Locales'] = {});
localeValues.forEach((localValue) => {
valueLocales[localValue.locale] = compileStr(value, localValue.values, delimiters);
});
}
}
else {
compileJsonObj(value, localeValues, delimiters);
}
}
function compileJsonObj(jsonObj, localeValues, delimiters) {
walkJsonObj(jsonObj, (jsonObj, key) => {
compileValue(jsonObj, key, localeValues, delimiters);
});
return jsonObj;
}
function walkJsonObj(jsonObj, walk) {
if (isArray(jsonObj)) {
for (let i = 0; i < jsonObj.length; i++) {
if (walk(jsonObj, i)) {
return true;
}
}
}
else if (isObject(jsonObj)) {
for (const key in jsonObj) {
if (walk(jsonObj, key)) {
return true;
}
}
}
return false;
}
exports.Formatter = BaseFormatter;
exports.I18n = I18n;
exports.LOCALE_EN = LOCALE_EN;
......@@ -299,4 +433,9 @@ exports.LOCALE_ES = LOCALE_ES;
exports.LOCALE_FR = LOCALE_FR;
exports.LOCALE_ZH_HANS = LOCALE_ZH_HANS;
exports.LOCALE_ZH_HANT = LOCALE_ZH_HANT;
exports.compileI18nJsonStr = compileI18nJsonStr;
exports.hasI18nJson = hasI18nJson;
exports.initVueI18n = initVueI18n;
exports.isI18nStr = isI18nStr;
exports.isString = isString;
exports.parseI18nJson = parseI18nJson;
export declare type BuiltInLocale = typeof LOCALE_ZH_HANS | typeof LOCALE_ZH_HANT | typeof LOCALE_EN | typeof LOCALE_FR | typeof LOCALE_ES;
export declare function compileI18nJsonStr(jsonStr: string, { locale, locales, delimiters, }: {
locale: string;
locales: Record<string, Record<string, string>>;
delimiters: [string, string];
}): string;
export declare class Formatter {
_caches: {
[key: string]: Array<Token>;
};
constructor();
interpolate(message: string, values?: Record<string, unknown> | Array<unknown>): Array<unknown>;
interpolate(message: string, values?: Record<string, unknown> | Array<unknown>, delimiters?: [string, string]): Array<unknown>;
}
declare interface Formatter_2 {
interpolate: (message: string, values?: Record<string, unknown> | Array<unknown>) => Array<unknown>;
interpolate: (message: string, values?: Record<string, unknown> | Array<unknown>, delimiters?: [string, string]) => Array<unknown>;
}
export declare function hasI18nJson(jsonObj: unknown, delimiters: [string, string]): boolean;
export declare class I18n {
private locale;
private fallbackLocale;
......@@ -23,7 +31,8 @@ export declare class I18n {
setLocale(locale: string): void;
getLocale(): BuiltInLocale;
watchLocale(fn: LocaleWatcher): () => void;
add(locale: BuiltInLocale, message: Record<string, string>): void;
add(locale: BuiltInLocale, message: Record<string, string>, override?: boolean): void;
f(message: string, values?: Record<string, unknown> | Array<unknown>): string;
t(key: string, values?: Record<string, unknown> | Array<unknown> | BuiltInLocale): string;
t(key: string, locale?: BuiltInLocale, values?: Record<string, unknown> | Array<unknown>): string;
}
......@@ -38,12 +47,18 @@ export declare interface I18nOptions {
export declare function initVueI18n(locale?: BuiltInLocale, messages?: LocaleMessages, fallbackLocale?: BuiltInLocale, watcher?: (locale: BuiltInLocale) => void): {
i18n: I18n;
f(message: string, values?: Record<string, unknown> | unknown[] | undefined): string;
t(key: string, values?: Record<string, unknown> | unknown[] | undefined): string;
add(locale: BuiltInLocale, message: Record<string, string>): void;
add(locale: BuiltInLocale, message: Record<string, string>, override?: boolean): void;
watch(fn: LocaleWatcher): () => void;
getLocale(): BuiltInLocale;
setLocale(newLocale: BuiltInLocale): void;
};
export declare function isI18nStr(value: string, delimiters: [string, string]): boolean;
export declare const isString: (val: unknown) => val is string;
export declare const LOCALE_EN = "en";
export declare const LOCALE_ES = "es";
......@@ -60,6 +75,8 @@ export declare type LocaleMessages = {
export declare type LocaleWatcher = (newLocale: BuiltInLocale, oldLocale: BuiltInLocale) => void;
export declare function parseI18nJson(jsonObj: unknown, values: Record<string, string>, delimiters: [string, string]): unknown;
declare type Token = {
type: 'text' | 'named' | 'list' | 'unknown';
value: string;
......
const isArray = Array.isArray;
const isObject = (val) => val !== null && typeof val === 'object';
const defaultDelimiters = ['{', '}'];
class BaseFormatter {
constructor() {
this._caches = Object.create(null);
}
interpolate(message, values) {
interpolate(message, values, delimiters = defaultDelimiters) {
if (!values) {
return [message];
}
let tokens = this._caches[message];
if (!tokens) {
tokens = parse(message);
tokens = parse(message, delimiters);
this._caches[message] = tokens;
}
return compile(tokens, values);
......@@ -17,24 +19,24 @@ class BaseFormatter {
}
const RE_TOKEN_LIST_VALUE = /^(?:\d)+/;
const RE_TOKEN_NAMED_VALUE = /^(?:\w)+/;
function parse(format) {
function parse(format, [startDelimiter, endDelimiter]) {
const tokens = [];
let position = 0;
let text = '';
while (position < format.length) {
let char = format[position++];
if (char === '{') {
if (char === startDelimiter) {
if (text) {
tokens.push({ type: 'text', value: text });
}
text = '';
let sub = '';
char = format[position++];
while (char !== undefined && char !== '}') {
while (char !== undefined && char !== endDelimiter) {
sub += char;
char = format[position++];
}
const isClosed = char === '}';
const isClosed = char === endDelimiter;
const type = RE_TOKEN_LIST_VALUE.test(sub)
? 'list'
: isClosed && RE_TOKEN_NAMED_VALUE.test(sub)
......@@ -42,12 +44,12 @@ function parse(format) {
: 'unknown';
tokens.push({ value: sub, type });
}
else if (char === '%') {
// when found rails i18n syntax, skip text capture
if (format[position] !== '{') {
text += char;
}
}
// else if (char === '%') {
// // when found rails i18n syntax, skip text capture
// if (format[position] !== '{') {
// text += char
// }
// }
else {
text += char;
}
......@@ -58,7 +60,7 @@ function parse(format) {
function compile(tokens, values) {
const compiled = [];
let index = 0;
const mode = Array.isArray(values)
const mode = isArray(values)
? 'list'
: isObject(values)
? 'named'
......@@ -161,9 +163,12 @@ class I18n {
this.messages[this.locale] = {};
}
this.message = this.messages[this.locale];
this.watchers.forEach((watcher) => {
watcher(this.locale, oldLocale);
});
// 仅发生变化时,通知
if (oldLocale !== this.locale) {
this.watchers.forEach((watcher) => {
watcher(this.locale, oldLocale);
});
}
}
getLocale() {
return this.locale;
......@@ -174,14 +179,27 @@ class I18n {
this.watchers.splice(index, 1);
};
}
add(locale, message) {
if (this.messages[locale]) {
Object.assign(this.messages[locale], message);
add(locale, message, override = true) {
const curMessages = this.messages[locale];
if (curMessages) {
if (override) {
Object.assign(curMessages, message);
}
else {
Object.keys(message).forEach((key) => {
if (!hasOwn(curMessages, key)) {
curMessages[key] = message[key];
}
});
}
}
else {
this.messages[locale] = message;
}
}
f(message, values) {
return this.formater.interpolate(message, values).join('');
}
t(key, locale, values) {
let message = this.message;
if (typeof locale === 'string') {
......@@ -220,6 +238,7 @@ function initLocaleWatcher(appVm, i18n) {
// }
// return uni.getSystemInfoSync().language
// }
const i18nInstances = [];
function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_EN, watcher) {
// 兼容旧版本入参
if (typeof locale !== 'string') {
......@@ -234,6 +253,7 @@ function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_
messages,
watcher,
});
i18nInstances.push(i18n);
let t = (key, values) => {
if (typeof getApp !== 'function') {
// app view
......@@ -273,19 +293,133 @@ function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_
};
return {
i18n,
f(message, values) {
return i18n.f(message, values);
},
t(key, values) {
return t(key, values);
},
add(locale, message) {
return i18n.add(locale, message);
add(locale, message, override = true) {
return i18n.add(locale, message, override);
},
watch(fn) {
return i18n.watchLocale(fn);
},
getLocale() {
return i18n.getLocale();
},
setLocale(newLocale) {
return i18n.setLocale(newLocale);
// 更新所有实例 locale
i18nInstances.forEach((ins) => {
ins.setLocale(newLocale);
});
},
};
}
export { BaseFormatter as Formatter, I18n, LOCALE_EN, LOCALE_ES, LOCALE_FR, LOCALE_ZH_HANS, LOCALE_ZH_HANT, initVueI18n };
const isString = (val) => typeof val === 'string';
let formater;
function hasI18nJson(jsonObj, delimiters) {
if (!formater) {
formater = new BaseFormatter();
}
return walkJsonObj(jsonObj, (jsonObj, key) => {
const value = jsonObj[key];
if (isString(value)) {
if (isI18nStr(value, delimiters)) {
return true;
}
}
else {
return hasI18nJson(value, delimiters);
}
});
}
function parseI18nJson(jsonObj, values, delimiters) {
if (!formater) {
formater = new BaseFormatter();
}
walkJsonObj(jsonObj, (jsonObj, key) => {
const value = jsonObj[key];
if (isString(value)) {
if (isI18nStr(value, delimiters)) {
jsonObj[key] = compileStr(value, values, delimiters);
}
}
else {
parseI18nJson(value, values, delimiters);
}
});
return jsonObj;
}
function compileI18nJsonStr(jsonStr, { locale, locales, delimiters, }) {
if (!isI18nStr(jsonStr, delimiters)) {
return jsonStr;
}
if (!formater) {
formater = new BaseFormatter();
}
const localeValues = [];
Object.keys(locales).forEach((name) => {
if (name !== locale) {
localeValues.push({
locale: name,
values: locales[name],
});
}
});
localeValues.unshift({ locale, values: locales[locale] });
try {
return JSON.stringify(compileJsonObj(JSON.parse(jsonStr), localeValues, delimiters), null, 2);
}
catch (e) { }
return jsonStr;
}
function isI18nStr(value, delimiters) {
return value.indexOf(delimiters[0]) > -1;
}
function compileStr(value, values, delimiters) {
return formater.interpolate(value, values, delimiters).join('');
}
function compileValue(jsonObj, key, localeValues, delimiters) {
const value = jsonObj[key];
if (isString(value)) {
// 存在国际化
if (isI18nStr(value, delimiters)) {
jsonObj[key] = compileStr(value, localeValues[0].values, delimiters);
// 格式化国际化语言
const valueLocales = (jsonObj[key + 'Locales'] = {});
localeValues.forEach((localValue) => {
valueLocales[localValue.locale] = compileStr(value, localValue.values, delimiters);
});
}
}
else {
compileJsonObj(value, localeValues, delimiters);
}
}
function compileJsonObj(jsonObj, localeValues, delimiters) {
walkJsonObj(jsonObj, (jsonObj, key) => {
compileValue(jsonObj, key, localeValues, delimiters);
});
return jsonObj;
}
function walkJsonObj(jsonObj, walk) {
if (isArray(jsonObj)) {
for (let i = 0; i < jsonObj.length; i++) {
if (walk(jsonObj, i)) {
return true;
}
}
}
else if (isObject(jsonObj)) {
for (const key in jsonObj) {
if (walk(jsonObj, key)) {
return true;
}
}
}
return false;
}
export { BaseFormatter as Formatter, I18n, LOCALE_EN, LOCALE_ES, LOCALE_FR, LOCALE_ZH_HANS, LOCALE_ZH_HANT, compileI18nJsonStr, hasI18nJson, initVueI18n, isI18nStr, isString, parseI18nJson };
......@@ -21,7 +21,8 @@ export type LocaleMessages = {
export interface Formatter {
interpolate: (
message: string,
values?: Record<string, unknown> | Array<unknown>
values?: Record<string, unknown> | Array<unknown>,
delimiters?: [string, string]
) => Array<unknown>
}
......@@ -113,9 +114,12 @@ export class I18n {
this.messages[this.locale] = {}
}
this.message = this.messages[this.locale]!
this.watchers.forEach((watcher) => {
watcher(this.locale, oldLocale)
})
// 仅发生变化时,通知
if (oldLocale !== this.locale) {
this.watchers.forEach((watcher) => {
watcher(this.locale, oldLocale)
})
}
}
getLocale() {
return this.locale
......@@ -126,13 +130,29 @@ export class I18n {
this.watchers.splice(index, 1)
}
}
add(locale: BuiltInLocale, message: Record<string, string>) {
if (this.messages[locale]) {
Object.assign(this.messages[locale], message)
add(
locale: BuiltInLocale,
message: Record<string, string>,
override: boolean = true
) {
const curMessages = this.messages[locale]
if (curMessages) {
if (override) {
Object.assign(curMessages, message)
} else {
Object.keys(message).forEach((key) => {
if (!hasOwn(curMessages, key)) {
curMessages[key] = message[key]
}
})
}
} else {
this.messages[locale] = message
}
}
f(message: string, values?: Record<string, unknown> | Array<unknown>) {
return this.formater.interpolate(message, values).join('')
}
t(
key: string,
values?: Record<string, unknown> | Array<unknown> | BuiltInLocale
......
const isObject = (val: unknown): val is Record<any, any> =>
export const isArray = Array.isArray
export const isObject = (val: unknown): val is Record<any, any> =>
val !== null && typeof val === 'object'
export const defaultDelimiters: [string, string] = ['{', '}']
export default class BaseFormatter {
_caches: { [key: string]: Array<Token> }
......@@ -10,14 +12,15 @@ export default class BaseFormatter {
interpolate(
message: string,
values?: Record<string, unknown> | Array<unknown>
values?: Record<string, unknown> | Array<unknown>,
delimiters: [string, string] = defaultDelimiters
): Array<unknown> {
if (!values) {
return [message]
}
let tokens: Array<Token> = this._caches[message]
if (!tokens) {
tokens = parse(message)
tokens = parse(message, delimiters)
this._caches[message] = tokens
}
return compile(tokens, values)
......@@ -32,14 +35,17 @@ type Token = {
const RE_TOKEN_LIST_VALUE: RegExp = /^(?:\d)+/
const RE_TOKEN_NAMED_VALUE: RegExp = /^(?:\w)+/
export function parse(format: string): Array<Token> {
export function parse(
format: string,
[startDelimiter, endDelimiter]: [string, string]
): Array<Token> {
const tokens: Array<Token> = []
let position: number = 0
let text: string = ''
while (position < format.length) {
let char: string = format[position++]
if (char === '{') {
if (char === startDelimiter) {
if (text) {
tokens.push({ type: 'text', value: text })
}
......@@ -47,11 +53,11 @@ export function parse(format: string): Array<Token> {
text = ''
let sub: string = ''
char = format[position++]
while (char !== undefined && char !== '}') {
while (char !== undefined && char !== endDelimiter) {
sub += char
char = format[position++]
}
const isClosed = char === '}'
const isClosed = char === endDelimiter
const type = RE_TOKEN_LIST_VALUE.test(sub)
? 'list'
......@@ -59,12 +65,14 @@ export function parse(format: string): Array<Token> {
? 'named'
: 'unknown'
tokens.push({ value: sub, type })
} else if (char === '%') {
// when found rails i18n syntax, skip text capture
if (format[position] !== '{') {
text += char
}
} else {
}
// else if (char === '%') {
// // when found rails i18n syntax, skip text capture
// if (format[position] !== '{') {
// text += char
// }
// }
else {
text += char
}
}
......@@ -81,7 +89,7 @@ export function compile(
const compiled: Array<unknown> = []
let index: number = 0
const mode: string = Array.isArray(values)
const mode: string = isArray(values)
? 'list'
: isObject(values)
? 'named'
......
export * from './I18n'
export * from './vue-i18n'
export * from './json'
export { default as Formatter } from './format'
import { isArray, isObject, default as BaseFormatter } from './format'
import { Formatter } from './I18n'
export const isString = (val: unknown): val is string => typeof val === 'string'
let formater: Formatter | null
interface LocaleValue {
locale: string
values: Record<string, unknown>
}
type LocaleValues = LocaleValue[]
export function hasI18nJson(
jsonObj: unknown,
delimiters: [string, string]
): boolean {
if (!formater) {
formater = new BaseFormatter()
}
return walkJsonObj(jsonObj, (jsonObj, key) => {
const value = (jsonObj as Record<string | number, unknown>)[key]
if (isString(value)) {
if (isI18nStr(value, delimiters)) {
return true
}
} else {
return hasI18nJson(value, delimiters)
}
})
}
export function parseI18nJson(
jsonObj: unknown,
values: Record<string, string>,
delimiters: [string, string]
) {
if (!formater) {
formater = new BaseFormatter()
}
walkJsonObj(jsonObj, (jsonObj, key) => {
const value = (jsonObj as Record<string | number, unknown>)[key]
if (isString(value)) {
if (isI18nStr(value, delimiters)) {
;(jsonObj as Record<string | number, unknown>)[key] = compileStr(
value,
values,
delimiters
)
}
} else {
parseI18nJson(value, values, delimiters)
}
})
return jsonObj
}
export function compileI18nJsonStr(
jsonStr: string,
{
locale,
locales,
delimiters,
}: {
locale: string
locales: Record<string, Record<string, string>>
delimiters: [string, string]
}
) {
if (!isI18nStr(jsonStr, delimiters)) {
return jsonStr
}
if (!formater) {
formater = new BaseFormatter()
}
const localeValues: LocaleValues = []
Object.keys(locales).forEach((name) => {
if (name !== locale) {
localeValues.push({
locale: name,
values: locales[name],
})
}
})
localeValues.unshift({ locale, values: locales[locale] })
try {
return JSON.stringify(
compileJsonObj(JSON.parse(jsonStr), localeValues, delimiters),
null,
2
)
} catch (e) {}
return jsonStr
}
export function isI18nStr(value: string, delimiters: [string, string]) {
return value.indexOf(delimiters[0]) > -1
}
function compileStr(
value: string,
values: LocaleValue['values'],
delimiters: [string, string]
) {
return formater!.interpolate(value, values, delimiters).join('')
}
function compileValue(
jsonObj: Record<string, unknown> | unknown[],
key: string | number,
localeValues: LocaleValues,
delimiters: [string, string]
) {
const value = (jsonObj as Record<string | number, unknown>)[key]
if (isString(value)) {
// 存在国际化
if (isI18nStr(value, delimiters)) {
// 格式化默认语言
;(jsonObj as Record<string | number, unknown>)[key] = compileStr(
value,
localeValues[0].values,
delimiters
)
// 格式化国际化语言
const valueLocales: Record<string, string> = ((
jsonObj as Record<string | number, unknown>
)[key + 'Locales'] = {})
localeValues.forEach((localValue) => {
valueLocales[localValue.locale] = compileStr(
value,
localValue.values,
delimiters
)
})
}
} else {
compileJsonObj(value, localeValues, delimiters)
}
}
function compileJsonObj(
jsonObj: unknown,
localeValues: LocaleValues,
delimiters: [string, string]
) {
walkJsonObj(jsonObj, (jsonObj, key) => {
compileValue(jsonObj, key, localeValues, delimiters)
})
return jsonObj
}
type WalkJson = (
jsonObj: unknown[] | Record<string, unknown>,
key: number | string
) => void | boolean
function walkJsonObj(jsonObj: unknown, walk: WalkJson) {
if (isArray(jsonObj)) {
for (let i = 0; i < jsonObj.length; i++) {
if (walk(jsonObj, i)) {
return true
}
}
} else if (isObject(jsonObj)) {
for (const key in jsonObj) {
if (walk(jsonObj, key)) {
return true
}
}
}
return false
}
import { I18n, BuiltInLocale, LocaleMessages, LOCALE_EN } from './I18n'
import {
I18n,
BuiltInLocale,
LocaleMessages,
LOCALE_EN,
LocaleWatcher,
} from './I18n'
const ignoreVueI18n = true
......@@ -33,6 +39,8 @@ function initLocaleWatcher(appVm: any, i18n: I18n) {
// return uni.getSystemInfoSync().language
// }
const i18nInstances: I18n[] = []
export function initVueI18n(
locale: BuiltInLocale = LOCALE_EN,
messages: LocaleMessages = {},
......@@ -52,6 +60,9 @@ export function initVueI18n(
messages,
watcher,
})
i18nInstances.push(i18n)
let t: Interpolate = (key, values) => {
if (typeof getApp !== 'function') {
// app view
......@@ -89,17 +100,30 @@ export function initVueI18n(
}
return {
i18n,
f(message: string, values?: Record<string, unknown> | Array<unknown>) {
return i18n.f(message, values)
},
t(key: string, values?: Record<string, unknown> | Array<unknown>) {
return t(key, values)
},
add(locale: BuiltInLocale, message: Record<string, string>) {
return i18n.add(locale, message)
add(
locale: BuiltInLocale,
message: Record<string, string>,
override: boolean = true
) {
return i18n.add(locale, message, override)
},
watch(fn: LocaleWatcher) {
return i18n.watchLocale(fn)
},
getLocale() {
return i18n.getLocale()
},
setLocale(newLocale: BuiltInLocale) {
return i18n.setLocale(newLocale)
// 更新所有实例 locale
i18nInstances.forEach((ins) => {
ins.setLocale(newLocale)
})
},
}
}
......@@ -948,6 +948,7 @@ const TABBAR_HEIGHT = 50;
const ON_REACH_BOTTOM_DISTANCE = 50;
const RESPONSIVE_MIN_WIDTH = 768;
const COMPONENT_NAME_PREFIX = 'VUni';
const I18N_JSON_DELIMITERS = ['%', '%'];
const PRIMARY_COLOR = '#007aff';
const SELECTED_COLOR = '#0062cc'; // 选中的颜色,如选项卡默认的选中颜色
const BACKGROUND_COLOR = '#f7f7f7'; // 背景色,如标题栏默认背景色
......@@ -1134,6 +1135,7 @@ exports.COMPONENT_SELECTOR_PREFIX = COMPONENT_SELECTOR_PREFIX;
exports.DATA_RE = DATA_RE;
exports.EventChannel = EventChannel;
exports.EventModifierFlags = EventModifierFlags;
exports.I18N_JSON_DELIMITERS = I18N_JSON_DELIMITERS;
exports.JSON_PROTOCOL = JSON_PROTOCOL;
exports.NAVBAR_HEIGHT = NAVBAR_HEIGHT;
exports.NODE_TYPE_COMMENT = NODE_TYPE_COMMENT;
......
/// <reference types="css-font-loading-module" />
import { ComponentInternalInstance } from 'vue';
import { ComponentOptionsBase } from 'vue';
import { ComponentPublicInstance } from 'vue';
import { FontFaceDescriptors } from 'css-font-loading-module';
import { RendererNode } from 'vue';
export declare const ACTION_TYPE_ADD_EVENT = 8;
......@@ -186,6 +187,8 @@ declare interface HTMLElementWithDataset extends HTMLElement {
__uniDataset?: Record<string, any>;
}
export declare const I18N_JSON_DELIMITERS: [string, string];
export declare function initCustomDataset(): void;
/**
......
......@@ -944,6 +944,7 @@ const TABBAR_HEIGHT = 50;
const ON_REACH_BOTTOM_DISTANCE = 50;
const RESPONSIVE_MIN_WIDTH = 768;
const COMPONENT_NAME_PREFIX = 'VUni';
const I18N_JSON_DELIMITERS = ['%', '%'];
const PRIMARY_COLOR = '#007aff';
const SELECTED_COLOR = '#0062cc'; // 选中的颜色,如选项卡默认的选中颜色
const BACKGROUND_COLOR = '#f7f7f7'; // 背景色,如标题栏默认背景色
......@@ -1101,4 +1102,4 @@ function getEnvLocale() {
return (lang && lang.replace(/[.:].*/, '')) || 'en';
}
export { ACTION_TYPE_ADD_EVENT, ACTION_TYPE_ADD_WXS_EVENT, ACTION_TYPE_CREATE, ACTION_TYPE_EVENT, ACTION_TYPE_INSERT, ACTION_TYPE_PAGE_CREATE, ACTION_TYPE_PAGE_CREATED, ACTION_TYPE_PAGE_SCROLL, ACTION_TYPE_REMOVE, ACTION_TYPE_REMOVE_ATTRIBUTE, ACTION_TYPE_REMOVE_EVENT, ACTION_TYPE_SET_ATTRIBUTE, ACTION_TYPE_SET_TEXT, ATTR_CHANGE_PREFIX, ATTR_CLASS, ATTR_INNER_HTML, ATTR_STYLE, ATTR_TEXT_CONTENT, ATTR_V_OWNER_ID, ATTR_V_RENDERJS, ATTR_V_SHOW, BACKGROUND_COLOR, BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, DATA_RE, EventChannel, EventModifierFlags, JSON_PROTOCOL, NAVBAR_HEIGHT, NODE_TYPE_COMMENT, NODE_TYPE_ELEMENT, NODE_TYPE_PAGE, NODE_TYPE_TEXT, ON_ADD_TO_FAVORITES, ON_APP_ENTER_BACKGROUND, ON_APP_ENTER_FOREGROUND, ON_BACK_PRESS, ON_ERROR, ON_HIDE, ON_KEYBOARD_HEIGHT_CHANGE, ON_LAUNCH, ON_LOAD, ON_NAVIGATION_BAR_BUTTON_TAP, ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED, ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED, ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED, ON_PAGE_NOT_FOUND, ON_PAGE_SCROLL, ON_PULL_DOWN_REFRESH, ON_REACH_BOTTOM, ON_REACH_BOTTOM_DISTANCE, ON_READY, ON_RESIZE, ON_SHARE_APP_MESSAGE, ON_SHARE_TIMELINE, ON_SHOW, ON_TAB_ITEM_TAP, ON_THEME_CHANGE, ON_UNHANDLE_REJECTION, ON_UNLOAD, ON_WEB_INVOKE_APP_SERVICE, ON_WXS_INVOKE_CALL_METHOD, PLUS_RE, PRIMARY_COLOR, RENDERJS_MODULES, RESPONSIVE_MIN_WIDTH, SCHEME_RE, SELECTED_COLOR, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, UNI_SSR_TITLE, UniBaseNode, UniCommentNode, UniElement, UniEvent, UniInputElement, UniLifecycleHooks, UniNode, UniTextAreaElement, UniTextNode, WEB_INVOKE_APPSERVICE, WXS_MODULES, WXS_PROTOCOL, addFont, cache, cacheStringFunction, callOptions, createRpx2Unit, createUniEvent, debounce, decode, decodedQuery, defaultRpx2Unit, formatAppLog, formatDateTime, formatLog, getCustomDataset, getEnvLocale, getLen, getValueByDataPath, initCustomDataset, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, isRootHook, isServiceCustomElement, isServiceNativeTag, normalizeDataset, normalizeEventType, normalizeTarget, once, parseEventName, parseQuery, parseUrl, passive, plusReady, removeLeadingSlash, resolveOwnerEl, resolveOwnerVm, sanitise, scrollTo, stringifyQuery, updateElementStyle };
export { ACTION_TYPE_ADD_EVENT, ACTION_TYPE_ADD_WXS_EVENT, ACTION_TYPE_CREATE, ACTION_TYPE_EVENT, ACTION_TYPE_INSERT, ACTION_TYPE_PAGE_CREATE, ACTION_TYPE_PAGE_CREATED, ACTION_TYPE_PAGE_SCROLL, ACTION_TYPE_REMOVE, ACTION_TYPE_REMOVE_ATTRIBUTE, ACTION_TYPE_REMOVE_EVENT, ACTION_TYPE_SET_ATTRIBUTE, ACTION_TYPE_SET_TEXT, ATTR_CHANGE_PREFIX, ATTR_CLASS, ATTR_INNER_HTML, ATTR_STYLE, ATTR_TEXT_CONTENT, ATTR_V_OWNER_ID, ATTR_V_RENDERJS, ATTR_V_SHOW, BACKGROUND_COLOR, BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, DATA_RE, EventChannel, EventModifierFlags, I18N_JSON_DELIMITERS, JSON_PROTOCOL, NAVBAR_HEIGHT, NODE_TYPE_COMMENT, NODE_TYPE_ELEMENT, NODE_TYPE_PAGE, NODE_TYPE_TEXT, ON_ADD_TO_FAVORITES, ON_APP_ENTER_BACKGROUND, ON_APP_ENTER_FOREGROUND, ON_BACK_PRESS, ON_ERROR, ON_HIDE, ON_KEYBOARD_HEIGHT_CHANGE, ON_LAUNCH, ON_LOAD, ON_NAVIGATION_BAR_BUTTON_TAP, ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED, ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED, ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED, ON_PAGE_NOT_FOUND, ON_PAGE_SCROLL, ON_PULL_DOWN_REFRESH, ON_REACH_BOTTOM, ON_REACH_BOTTOM_DISTANCE, ON_READY, ON_RESIZE, ON_SHARE_APP_MESSAGE, ON_SHARE_TIMELINE, ON_SHOW, ON_TAB_ITEM_TAP, ON_THEME_CHANGE, ON_UNHANDLE_REJECTION, ON_UNLOAD, ON_WEB_INVOKE_APP_SERVICE, ON_WXS_INVOKE_CALL_METHOD, PLUS_RE, PRIMARY_COLOR, RENDERJS_MODULES, RESPONSIVE_MIN_WIDTH, SCHEME_RE, SELECTED_COLOR, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, UNI_SSR_TITLE, UniBaseNode, UniCommentNode, UniElement, UniEvent, UniInputElement, UniLifecycleHooks, UniNode, UniTextAreaElement, UniTextNode, WEB_INVOKE_APPSERVICE, WXS_MODULES, WXS_PROTOCOL, addFont, cache, cacheStringFunction, callOptions, createRpx2Unit, createUniEvent, debounce, decode, decodedQuery, defaultRpx2Unit, formatAppLog, formatDateTime, formatLog, getCustomDataset, getEnvLocale, getLen, getValueByDataPath, initCustomDataset, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, isRootHook, isServiceCustomElement, isServiceNativeTag, normalizeDataset, normalizeEventType, normalizeTarget, once, parseEventName, parseQuery, parseUrl, passive, plusReady, removeLeadingSlash, resolveOwnerEl, resolveOwnerVm, sanitise, scrollTo, stringifyQuery, updateElementStyle };
......@@ -5,6 +5,8 @@ export const RESPONSIVE_MIN_WIDTH = 768
export const COMPONENT_NAME_PREFIX = 'VUni'
export const I18N_JSON_DELIMITERS: [string, string] = ['%', '%']
export const PRIMARY_COLOR = '#007aff'
export const SELECTED_COLOR = '#0062cc' // 选中的颜色,如选项卡默认的选中颜色
export const BACKGROUND_COLOR = '#f7f7f7' // 背景色,如标题栏默认背景色
......
import { FontFaceDescriptors } from 'css-font-loading-module'
import { isString } from '@vue/shared'
import { getCustomDataset } from './customDataset'
export { initCustomDataset, getCustomDataset } from './customDataset'
......
'use strict';
var version = "3.0.0-alpha-3000020210827003";
var version = "3.0.0-alpha-3000020210827004";
const STAT_VERSION = version;
const STAT_URL = 'https://tongji.dcloud.io/uni/stat';
......
var version = "3.0.0-alpha-3000020210827003";
var version = "3.0.0-alpha-3000020210827004";
const STAT_VERSION = version;
const STAT_URL = 'https://tongji.dcloud.io/uni/stat';
......
......@@ -53,6 +53,7 @@ export function uniResolveIdPlugin(
path.join(id, BUILT_IN_MODULES[id as BuiltInModulesKey])
))
}
// fixed by vite 3.5.2 https://github.com/vitejs/vite/pull/4728
if (isInHBuilderX()) {
// 解决文件路径包含转义字符(空格)等
// /@fs/Applications/HBuilderX%20Alpha.app/Contents/HBuilderX/plugins/uniapp-cli-vite/node_modules/vite/dist/client/env.mjs
......
......@@ -4,15 +4,20 @@ const locales = ['en', 'es', 'fr', 'zh-Hans', 'zh-Hant']
function buildI18n(namespace, dir) {
const modules = {}
locales.forEach((locale) => {
// modules
// {app: { keys: ['quit'], values:{'zh-Hans': ['退出']}}}
locales.forEach((locale, index) => {
const messages = buildI18nLocale(locale, namespace, dir)
Object.keys(messages).forEach((moduleName) => {
;(modules[moduleName] || (modules[moduleName] = {}))[locale] =
messages[moduleName]
if (!modules[moduleName]) {
modules[moduleName] = {
keys: Object.keys(messages[moduleName]),
values: {},
}
}
modules[moduleName].values[locale] = Object.values(messages[moduleName])
})
})
// modules
// {app: {en: { quit: '' },es: { quit: '' },fr: { quit: "" },'zh-Hans': { quit: '' },'zh-Hant': { quit: '' }}}
const messagesFile = path.resolve(dir, 'messages.ts')
fs.writeFileSync(messagesFile, generateI18nCode(namespace, modules))
console.log('write:' + messagesFile)
......@@ -52,12 +57,9 @@ import {
} from '@dcloudio/uni-i18n'
import { useI18n } from './useI18n'
function normalizeMessages(
namespace: string,
messages: Record<string, string>
) {
return Object.keys(messages).reduce<Record<string, string>>((res, name) => {
res[namespace + name] = messages[name]
function normalizeMessages(module: string, keys: string[], values: string[]) {
return keys.reduce<Record<string, string>>((res, name, index) => {
res[module + name] = values[index]
return res
}, {})
}
......@@ -72,9 +74,12 @@ function generateI18nModuleCode(namespace, name, localeMessages) {
return `export const initI18n${capitalize(
name
)}MsgsOnce = /*#__PURE__*/ once(()=> {
const name = '${namespace}.${name}.'
${Object.keys(localeMessages)
.map((locale) => generateI18nModuleLocaleCode(locale, localeMessages[locale]))
const name = '${namespace}.${name}.'
const keys = ${JSON.stringify(localeMessages.keys)}
${Object.keys(localeMessages.values)
.map((locale) =>
generateI18nModuleLocaleCode(locale, localeMessages.values[locale])
)
.join('')}
})
`
......@@ -83,9 +88,9 @@ ${Object.keys(localeMessages)
function generateI18nModuleLocaleCode(locale, messages) {
locale = locale.toUpperCase().replace('-', '_')
return ` if (__UNI_FEATURE_I18N_${locale}__) {
useI18n().add(LOCALE_${locale}, normalizeMessages(name, ${JSON.stringify(
useI18n().add(LOCALE_${locale}, normalizeMessages(name, keys, ${JSON.stringify(
messages
)}))
)}), false)
}
`
}
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册