mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
feat(image-conversion): add queue management for image conversion tasks and update loading states
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
||||
interface LoadingState {
|
||||
isVisible: boolean
|
||||
isConverting: boolean
|
||||
isQueueWaiting: boolean
|
||||
isHeicFormat: boolean
|
||||
loadingProgress: number
|
||||
loadedBytes: number
|
||||
@@ -34,6 +35,7 @@ const initialLoadingState: LoadingState = {
|
||||
loadedBytes: 0,
|
||||
totalBytes: 0,
|
||||
conversionMessage: undefined,
|
||||
isQueueWaiting: false,
|
||||
|
||||
isWebGLLoading: false,
|
||||
webglMessage: undefined,
|
||||
@@ -99,7 +101,9 @@ export const LoadingIndicator = ({
|
||||
// 视频转换状态
|
||||
<>
|
||||
<p className="text-xs font-medium text-white tabular-nums">
|
||||
{loadingState.conversionMessage || t('loading.converting')}
|
||||
{loadingState.isQueueWaiting
|
||||
? loadingState.conversionMessage || t('loading.queue.waiting')
|
||||
: loadingState.conversionMessage || t('loading.converting')}
|
||||
</p>
|
||||
</>
|
||||
) : loadingState.isWebGLLoading ? (
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
* 图像转换策略模式实现
|
||||
* 支持多种浏览器原生不支持的图片格式转换
|
||||
*/
|
||||
import { i18nAtom } from '~/i18n'
|
||||
import { jotaiStore } from '~/lib/jotai'
|
||||
|
||||
import type { LoadingCallbacks } from '../image-loader-manager'
|
||||
import type { PipelineOptions } from './pipeline'
|
||||
import { ImageConversionPipeline } from './pipeline'
|
||||
import { HeicConverterStrategy } from './strategies/heic'
|
||||
import { TiffConverterStrategy } from './strategies/tiff'
|
||||
import type { ConversionResult, ImageConverterStrategy } from './type'
|
||||
@@ -10,8 +15,16 @@ import type { ConversionResult, ImageConverterStrategy } from './type'
|
||||
// 图像转换策略管理器
|
||||
export class ImageConverterManager {
|
||||
private strategies = new Map<string, ImageConverterStrategy>()
|
||||
private readonly conversionPipeline: ImageConversionPipeline
|
||||
private readonly pendingConversions = new Map<
|
||||
string,
|
||||
Promise<ConversionResult>
|
||||
>()
|
||||
|
||||
constructor() {
|
||||
constructor(options: PipelineOptions = {}) {
|
||||
this.conversionPipeline = new ImageConversionPipeline({
|
||||
maxConcurrent: options.maxConcurrent ?? 2,
|
||||
})
|
||||
// 注册默认策略
|
||||
this.registerStrategy(new HeicConverterStrategy())
|
||||
this.registerStrategy(new TiffConverterStrategy())
|
||||
@@ -120,7 +133,44 @@ export class ImageConverterManager {
|
||||
}
|
||||
|
||||
console.info(`Converting image using ${strategy.getName()} strategy`)
|
||||
return await strategy.convert(blob, originalUrl, callbacks)
|
||||
const taskKey = this.getConversionTaskKey(strategy, originalUrl)
|
||||
|
||||
const onLoadingStateUpdate = callbacks?.onLoadingStateUpdate
|
||||
const pipelineActive = this.conversionPipeline.getActiveCount()
|
||||
const maxConcurrent = this.conversionPipeline.getMaxConcurrent()
|
||||
const isPipelineSaturated = pipelineActive >= maxConcurrent
|
||||
|
||||
const existingTask = this.pendingConversions.get(taskKey)
|
||||
if (existingTask) {
|
||||
console.info(
|
||||
`Joining pending conversion task for ${strategy.getName()} (${originalUrl})`,
|
||||
)
|
||||
return await existingTask
|
||||
}
|
||||
|
||||
if (onLoadingStateUpdate && isPipelineSaturated) {
|
||||
const i18n = jotaiStore.get(i18nAtom)
|
||||
onLoadingStateUpdate({
|
||||
isConverting: true,
|
||||
isQueueWaiting: true,
|
||||
conversionMessage: i18n.t('loading.queue.waiting'),
|
||||
})
|
||||
}
|
||||
|
||||
const conversionPromise = this.conversionPipeline.enqueue(async () => {
|
||||
try {
|
||||
onLoadingStateUpdate?.({
|
||||
isQueueWaiting: false,
|
||||
conversionMessage: undefined,
|
||||
})
|
||||
return await strategy.convert(blob, originalUrl, callbacks)
|
||||
} finally {
|
||||
this.pendingConversions.delete(taskKey)
|
||||
}
|
||||
})
|
||||
|
||||
this.pendingConversions.set(taskKey, conversionPromise)
|
||||
return await conversionPromise
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +179,30 @@ export class ImageConverterManager {
|
||||
getSupportedFormats(): string[] {
|
||||
return Array.from(this.strategies.keys())
|
||||
}
|
||||
|
||||
getPipelineStats(): {
|
||||
active: number
|
||||
pending: number
|
||||
} {
|
||||
return {
|
||||
active: this.conversionPipeline.getActiveCount(),
|
||||
pending: this.conversionPipeline.getPendingCount(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整管道的最大并发转换数量
|
||||
*/
|
||||
setMaxConcurrentConversions(maxConcurrent: number): void {
|
||||
this.conversionPipeline.setMaxConcurrent(maxConcurrent)
|
||||
}
|
||||
|
||||
private getConversionTaskKey(
|
||||
strategy: ImageConverterStrategy,
|
||||
originalUrl: string,
|
||||
): string {
|
||||
return `${strategy.getName()}::${originalUrl}`
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
||||
90
apps/web/src/lib/image-convert/pipeline.ts
Normal file
90
apps/web/src/lib/image-convert/pipeline.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
export interface PipelineOptions {
|
||||
maxConcurrent?: number
|
||||
}
|
||||
|
||||
type TaskExecutor<T> = () => Promise<T>
|
||||
|
||||
type QueueTask<T> = {
|
||||
execute: TaskExecutor<T>
|
||||
resolve: (value: T) => void
|
||||
reject: (reason?: unknown) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight promise queue to throttle image conversion workloads
|
||||
*/
|
||||
export class ImageConversionPipeline {
|
||||
private maxConcurrent: number
|
||||
private readonly queue: Array<QueueTask<any>> = []
|
||||
private activeCount = 0
|
||||
|
||||
constructor(options: PipelineOptions = {}) {
|
||||
const { maxConcurrent = 2 } = options
|
||||
this.maxConcurrent = Math.max(1, maxConcurrent)
|
||||
}
|
||||
|
||||
enqueue<T>(task: TaskExecutor<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const queueTask: QueueTask<T> = {
|
||||
execute: task,
|
||||
resolve,
|
||||
reject,
|
||||
}
|
||||
|
||||
this.queue.push(queueTask)
|
||||
this.drainQueue()
|
||||
})
|
||||
}
|
||||
|
||||
getActiveCount(): number {
|
||||
return this.activeCount
|
||||
}
|
||||
|
||||
getPendingCount(): number {
|
||||
return this.queue.length
|
||||
}
|
||||
|
||||
getMaxConcurrent(): number {
|
||||
return this.maxConcurrent
|
||||
}
|
||||
|
||||
setMaxConcurrent(maxConcurrent: number): void {
|
||||
if (!Number.isFinite(maxConcurrent)) {
|
||||
throw new TypeError('maxConcurrent must be a finite number')
|
||||
}
|
||||
|
||||
const normalizedValue = Math.max(1, Math.floor(maxConcurrent))
|
||||
if (normalizedValue === this.maxConcurrent) {
|
||||
return
|
||||
}
|
||||
|
||||
this.maxConcurrent = normalizedValue
|
||||
this.drainQueue()
|
||||
}
|
||||
|
||||
private drainQueue(): void {
|
||||
if (this.activeCount >= this.maxConcurrent) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextTask = this.queue.shift()
|
||||
if (!nextTask) {
|
||||
return
|
||||
}
|
||||
|
||||
this.activeCount += 1
|
||||
|
||||
// Run task asynchronously without blocking
|
||||
;(async () => {
|
||||
try {
|
||||
const result = await nextTask.execute()
|
||||
nextTask.resolve(result)
|
||||
} catch (error) {
|
||||
nextTask.reject(error)
|
||||
} finally {
|
||||
this.activeCount = Math.max(0, this.activeCount - 1)
|
||||
this.drainQueue()
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ export class HeicConverterStrategy implements ImageConverterStrategy {
|
||||
// 更新转换状态
|
||||
onLoadingStateUpdate?.({
|
||||
isConverting: true,
|
||||
isQueueWaiting: false,
|
||||
conversionMessage: i18n.t('loading.heic.converting'),
|
||||
isHeicFormat: true,
|
||||
loadingProgress: 100,
|
||||
|
||||
@@ -28,6 +28,7 @@ export class TiffConverterStrategy implements ImageConverterStrategy {
|
||||
// 更新转换状态
|
||||
onLoadingStateUpdate?.({
|
||||
isConverting: true,
|
||||
isQueueWaiting: false,
|
||||
conversionMessage: 'Converting TIFF image...',
|
||||
})
|
||||
|
||||
@@ -48,7 +49,7 @@ export class TiffConverterStrategy implements ImageConverterStrategy {
|
||||
|
||||
// 浏览器支持检测
|
||||
private isBrowserSupportTiff(): boolean {
|
||||
// safari 支持tiff
|
||||
// safari 支持 tiff
|
||||
if (isSafari) {
|
||||
return true
|
||||
}
|
||||
@@ -147,7 +148,7 @@ export class TiffConverterStrategy implements ImageConverterStrategy {
|
||||
break
|
||||
}
|
||||
case 16: {
|
||||
// 16位数据,需要转换为8位
|
||||
// 16 位数据,需要转换为 8 位
|
||||
const data = sourceData as Uint16Array
|
||||
targetData[dstIndex] = Math.round((data[srcIndex] || 0) / 257) // R
|
||||
targetData[dstIndex + 1] =
|
||||
@@ -165,7 +166,7 @@ export class TiffConverterStrategy implements ImageConverterStrategy {
|
||||
break
|
||||
}
|
||||
case 32: {
|
||||
// 32位浮点数据
|
||||
// 32 位浮点数据
|
||||
const data = sourceData as Float32Array | Float64Array
|
||||
targetData[dstIndex] = Math.round((data[srcIndex] || 0) * 255) // R
|
||||
targetData[dstIndex + 1] =
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface LoadingState {
|
||||
loadedBytes?: number
|
||||
totalBytes?: number
|
||||
isConverting?: boolean
|
||||
isQueueWaiting?: boolean
|
||||
conversionMessage?: string
|
||||
codecInfo?: string
|
||||
}
|
||||
|
||||
@@ -316,6 +316,7 @@
|
||||
"loading.default": "Loading",
|
||||
"loading.heic.converting": "Converting HEIC/HEIF image format...",
|
||||
"loading.heic.main": "HEIC",
|
||||
"loading.queue.waiting": "Waiting for available converter...",
|
||||
"loading.webgl.building": "Building high-quality textures...",
|
||||
"loading.webgl.main": "WebGL Texture Loading",
|
||||
"minimap.loading": "Loading map...",
|
||||
@@ -377,4 +378,4 @@
|
||||
"video.conversion.webcodecs.not.supported": "WebCodecs is not supported in this browser",
|
||||
"video.format.mov.not.supported": "Browser does not support MOV format, conversion required",
|
||||
"video.format.mov.supported": "Browser natively supports MOV format, skipping conversion"
|
||||
}
|
||||
}
|
||||
@@ -41,37 +41,37 @@
|
||||
"action.view.layout": "レイアウト",
|
||||
"action.view.settings": "ビュー設定",
|
||||
"action.view.title": "ビュー",
|
||||
"date.day.1": "1日",
|
||||
"date.day.10": "10日",
|
||||
"date.day.11": "11日",
|
||||
"date.day.12": "12日",
|
||||
"date.day.13": "13日",
|
||||
"date.day.14": "14日",
|
||||
"date.day.15": "15日",
|
||||
"date.day.16": "16日",
|
||||
"date.day.17": "17日",
|
||||
"date.day.18": "18日",
|
||||
"date.day.19": "19日",
|
||||
"date.day.2": "2日",
|
||||
"date.day.20": "20日",
|
||||
"date.day.21": "21日",
|
||||
"date.day.22": "22日",
|
||||
"date.day.23": "23日",
|
||||
"date.day.24": "24日",
|
||||
"date.day.25": "25日",
|
||||
"date.day.26": "26日",
|
||||
"date.day.27": "27日",
|
||||
"date.day.28": "28日",
|
||||
"date.day.29": "29日",
|
||||
"date.day.3": "3日",
|
||||
"date.day.30": "30日",
|
||||
"date.day.31": "31日",
|
||||
"date.day.4": "4日",
|
||||
"date.day.5": "5日",
|
||||
"date.day.6": "6日",
|
||||
"date.day.7": "7日",
|
||||
"date.day.8": "8日",
|
||||
"date.day.9": "9日",
|
||||
"date.day.1": "1 日",
|
||||
"date.day.10": "10 日",
|
||||
"date.day.11": "11 日",
|
||||
"date.day.12": "12 日",
|
||||
"date.day.13": "13 日",
|
||||
"date.day.14": "14 日",
|
||||
"date.day.15": "15 日",
|
||||
"date.day.16": "16 日",
|
||||
"date.day.17": "17 日",
|
||||
"date.day.18": "18 日",
|
||||
"date.day.19": "19 日",
|
||||
"date.day.2": "2 日",
|
||||
"date.day.20": "20 日",
|
||||
"date.day.21": "21 日",
|
||||
"date.day.22": "22 日",
|
||||
"date.day.23": "23 日",
|
||||
"date.day.24": "24 日",
|
||||
"date.day.25": "25 日",
|
||||
"date.day.26": "26 日",
|
||||
"date.day.27": "27 日",
|
||||
"date.day.28": "28 日",
|
||||
"date.day.29": "29 日",
|
||||
"date.day.3": "3 日",
|
||||
"date.day.30": "30 日",
|
||||
"date.day.31": "31 日",
|
||||
"date.day.4": "4 日",
|
||||
"date.day.5": "5 日",
|
||||
"date.day.6": "6 日",
|
||||
"date.day.7": "7 日",
|
||||
"date.day.8": "8 日",
|
||||
"date.day.9": "9 日",
|
||||
"date.month.1": "1月",
|
||||
"date.month.10": "10月",
|
||||
"date.month.11": "11月",
|
||||
@@ -312,6 +312,7 @@
|
||||
"loading.default": "読み込み中",
|
||||
"loading.heic.converting": "HEIC/HEIF 画像フォーマットを変換中...",
|
||||
"loading.heic.main": "HEIC",
|
||||
"loading.queue.waiting": "変換待機中です...",
|
||||
"loading.webgl.building": "高品質テクスチャを構築中...",
|
||||
"loading.webgl.main": "WebGL テクスチャの読み込み",
|
||||
"minimap.loading": "地図を読み込み中...",
|
||||
@@ -370,4 +371,4 @@
|
||||
"video.conversion.webcodecs.not.supported": "このブラウザは WebCodecs をサポートしていません",
|
||||
"video.format.mov.not.supported": "ブラウザが MOV 形式をサポートしていないため、変換が必要です",
|
||||
"video.format.mov.supported": "ブラウザが MOV 形式をネイティブでサポートしているため、変換をスキップします"
|
||||
}
|
||||
}
|
||||
@@ -41,37 +41,37 @@
|
||||
"action.view.layout": "레이아웃",
|
||||
"action.view.settings": "보기 설정",
|
||||
"action.view.title": "보기",
|
||||
"date.day.1": "1일",
|
||||
"date.day.10": "10일",
|
||||
"date.day.11": "11일",
|
||||
"date.day.12": "12일",
|
||||
"date.day.13": "13일",
|
||||
"date.day.14": "14일",
|
||||
"date.day.15": "15일",
|
||||
"date.day.16": "16일",
|
||||
"date.day.17": "17일",
|
||||
"date.day.18": "18일",
|
||||
"date.day.19": "19일",
|
||||
"date.day.2": "2일",
|
||||
"date.day.20": "20일",
|
||||
"date.day.21": "21일",
|
||||
"date.day.22": "22일",
|
||||
"date.day.23": "23일",
|
||||
"date.day.24": "24일",
|
||||
"date.day.25": "25일",
|
||||
"date.day.26": "26일",
|
||||
"date.day.27": "27일",
|
||||
"date.day.28": "28일",
|
||||
"date.day.29": "29일",
|
||||
"date.day.3": "3일",
|
||||
"date.day.30": "30일",
|
||||
"date.day.31": "31일",
|
||||
"date.day.4": "4일",
|
||||
"date.day.5": "5일",
|
||||
"date.day.6": "6일",
|
||||
"date.day.7": "7일",
|
||||
"date.day.8": "8일",
|
||||
"date.day.9": "9일",
|
||||
"date.day.1": "1 일",
|
||||
"date.day.10": "10 일",
|
||||
"date.day.11": "11 일",
|
||||
"date.day.12": "12 일",
|
||||
"date.day.13": "13 일",
|
||||
"date.day.14": "14 일",
|
||||
"date.day.15": "15 일",
|
||||
"date.day.16": "16 일",
|
||||
"date.day.17": "17 일",
|
||||
"date.day.18": "18 일",
|
||||
"date.day.19": "19 일",
|
||||
"date.day.2": "2 일",
|
||||
"date.day.20": "20 일",
|
||||
"date.day.21": "21 일",
|
||||
"date.day.22": "22 일",
|
||||
"date.day.23": "23 일",
|
||||
"date.day.24": "24 일",
|
||||
"date.day.25": "25 일",
|
||||
"date.day.26": "26 일",
|
||||
"date.day.27": "27 일",
|
||||
"date.day.28": "28 일",
|
||||
"date.day.29": "29 일",
|
||||
"date.day.3": "3 일",
|
||||
"date.day.30": "30 일",
|
||||
"date.day.31": "31 일",
|
||||
"date.day.4": "4 일",
|
||||
"date.day.5": "5 일",
|
||||
"date.day.6": "6 일",
|
||||
"date.day.7": "7 일",
|
||||
"date.day.8": "8 일",
|
||||
"date.day.9": "9 일",
|
||||
"date.month.1": "1월",
|
||||
"date.month.10": "10월",
|
||||
"date.month.11": "11월",
|
||||
@@ -312,6 +312,7 @@
|
||||
"loading.default": "로딩 중",
|
||||
"loading.heic.converting": "HEIC/HEIF 이미지 형식 변환 중...",
|
||||
"loading.heic.main": "HEIC",
|
||||
"loading.queue.waiting": "변환 대기 중입니다...",
|
||||
"loading.webgl.building": "고품질 텍스처 구축 중...",
|
||||
"loading.webgl.main": "WebGL 텍스처 로딩",
|
||||
"minimap.loading": "지도 로딩 중...",
|
||||
@@ -370,4 +371,4 @@
|
||||
"video.conversion.webcodecs.not.supported": "이 브라우저는 WebCodecs 를 지원하지 않습니다",
|
||||
"video.format.mov.not.supported": "브라우저가 MOV 형식을 지원하지 않아 변환이 필요합니다.",
|
||||
"video.format.mov.supported": "브라우저가 MOV 형식을 기본적으로 지원하므로 변환을 건너뜁니다."
|
||||
}
|
||||
}
|
||||
@@ -313,6 +313,7 @@
|
||||
"loading.default": "加载中",
|
||||
"loading.heic.converting": "正在转换 HEIC/HEIF 图像格式...",
|
||||
"loading.heic.main": "HEIC",
|
||||
"loading.queue.waiting": "正在排队等待转换...",
|
||||
"loading.webgl.building": "正在构建高质量纹理...",
|
||||
"loading.webgl.main": "WebGL 纹理加载",
|
||||
"minimap.loading": "加载地图中...",
|
||||
@@ -374,4 +375,4 @@
|
||||
"video.conversion.webcodecs.not.supported": "此浏览器不支持 WebCodecs",
|
||||
"video.format.mov.not.supported": "浏览器不支持 MOV 格式,需要转换",
|
||||
"video.format.mov.supported": "浏览器原生支持 MOV 格式,跳过转换"
|
||||
}
|
||||
}
|
||||
@@ -41,37 +41,37 @@
|
||||
"action.view.layout": "佈局",
|
||||
"action.view.settings": "檢視設定",
|
||||
"action.view.title": "檢視",
|
||||
"date.day.1": "1日",
|
||||
"date.day.10": "10日",
|
||||
"date.day.11": "11日",
|
||||
"date.day.12": "12日",
|
||||
"date.day.13": "13日",
|
||||
"date.day.14": "14日",
|
||||
"date.day.15": "15日",
|
||||
"date.day.16": "16日",
|
||||
"date.day.17": "17日",
|
||||
"date.day.18": "18日",
|
||||
"date.day.19": "19日",
|
||||
"date.day.2": "2日",
|
||||
"date.day.20": "20日",
|
||||
"date.day.21": "21日",
|
||||
"date.day.22": "22日",
|
||||
"date.day.23": "23日",
|
||||
"date.day.24": "24日",
|
||||
"date.day.25": "25日",
|
||||
"date.day.26": "26日",
|
||||
"date.day.27": "27日",
|
||||
"date.day.28": "28日",
|
||||
"date.day.29": "29日",
|
||||
"date.day.3": "3日",
|
||||
"date.day.30": "30日",
|
||||
"date.day.31": "31日",
|
||||
"date.day.4": "4日",
|
||||
"date.day.5": "5日",
|
||||
"date.day.6": "6日",
|
||||
"date.day.7": "7日",
|
||||
"date.day.8": "8日",
|
||||
"date.day.9": "9日",
|
||||
"date.day.1": "1 日",
|
||||
"date.day.10": "10 日",
|
||||
"date.day.11": "11 日",
|
||||
"date.day.12": "12 日",
|
||||
"date.day.13": "13 日",
|
||||
"date.day.14": "14 日",
|
||||
"date.day.15": "15 日",
|
||||
"date.day.16": "16 日",
|
||||
"date.day.17": "17 日",
|
||||
"date.day.18": "18 日",
|
||||
"date.day.19": "19 日",
|
||||
"date.day.2": "2 日",
|
||||
"date.day.20": "20 日",
|
||||
"date.day.21": "21 日",
|
||||
"date.day.22": "22 日",
|
||||
"date.day.23": "23 日",
|
||||
"date.day.24": "24 日",
|
||||
"date.day.25": "25 日",
|
||||
"date.day.26": "26 日",
|
||||
"date.day.27": "27 日",
|
||||
"date.day.28": "28 日",
|
||||
"date.day.29": "29 日",
|
||||
"date.day.3": "3 日",
|
||||
"date.day.30": "30 日",
|
||||
"date.day.31": "31 日",
|
||||
"date.day.4": "4 日",
|
||||
"date.day.5": "5 日",
|
||||
"date.day.6": "6 日",
|
||||
"date.day.7": "7 日",
|
||||
"date.day.8": "8 日",
|
||||
"date.day.9": "9 日",
|
||||
"date.month.1": "1月",
|
||||
"date.month.10": "7月",
|
||||
"date.month.11": "8月",
|
||||
@@ -312,6 +312,7 @@
|
||||
"loading.default": "載入中",
|
||||
"loading.heic.converting": "正在轉換 HEIC/HEIF 圖像格式...",
|
||||
"loading.heic.main": "HEIC",
|
||||
"loading.queue.waiting": "正在排隊等候轉換...",
|
||||
"loading.webgl.building": "正在建置高品質紋理...",
|
||||
"loading.webgl.main": "WebGL 紋理載入",
|
||||
"minimap.loading": "載入地圖中...",
|
||||
@@ -370,4 +371,4 @@
|
||||
"video.conversion.webcodecs.not.supported": "此瀏覽器不支援 WebCodecs",
|
||||
"video.format.mov.not.supported": "瀏覽器不支援 MOV 格式,需要轉換",
|
||||
"video.format.mov.supported": "瀏覽器原生支援 MOV 格式,跳過轉換"
|
||||
}
|
||||
}
|
||||
@@ -41,37 +41,37 @@
|
||||
"action.view.layout": "佈局",
|
||||
"action.view.settings": "檢視設定",
|
||||
"action.view.title": "檢視",
|
||||
"date.day.1": "1日",
|
||||
"date.day.10": "10日",
|
||||
"date.day.11": "11日",
|
||||
"date.day.12": "12日",
|
||||
"date.day.13": "13日",
|
||||
"date.day.14": "14日",
|
||||
"date.day.15": "15日",
|
||||
"date.day.16": "16日",
|
||||
"date.day.17": "17日",
|
||||
"date.day.18": "18日",
|
||||
"date.day.19": "19日",
|
||||
"date.day.2": "2日",
|
||||
"date.day.20": "20日",
|
||||
"date.day.21": "21日",
|
||||
"date.day.22": "22日",
|
||||
"date.day.23": "23日",
|
||||
"date.day.24": "24日",
|
||||
"date.day.25": "25日",
|
||||
"date.day.26": "26日",
|
||||
"date.day.27": "27日",
|
||||
"date.day.28": "28日",
|
||||
"date.day.29": "29日",
|
||||
"date.day.3": "3日",
|
||||
"date.day.30": "30日",
|
||||
"date.day.31": "31日",
|
||||
"date.day.4": "4日",
|
||||
"date.day.5": "5日",
|
||||
"date.day.6": "6日",
|
||||
"date.day.7": "7日",
|
||||
"date.day.8": "8日",
|
||||
"date.day.9": "9日",
|
||||
"date.day.1": "1 日",
|
||||
"date.day.10": "10 日",
|
||||
"date.day.11": "11 日",
|
||||
"date.day.12": "12 日",
|
||||
"date.day.13": "13 日",
|
||||
"date.day.14": "14 日",
|
||||
"date.day.15": "15 日",
|
||||
"date.day.16": "16 日",
|
||||
"date.day.17": "17 日",
|
||||
"date.day.18": "18 日",
|
||||
"date.day.19": "19 日",
|
||||
"date.day.2": "2 日",
|
||||
"date.day.20": "20 日",
|
||||
"date.day.21": "21 日",
|
||||
"date.day.22": "22 日",
|
||||
"date.day.23": "23 日",
|
||||
"date.day.24": "24 日",
|
||||
"date.day.25": "25 日",
|
||||
"date.day.26": "26 日",
|
||||
"date.day.27": "27 日",
|
||||
"date.day.28": "28 日",
|
||||
"date.day.29": "29 日",
|
||||
"date.day.3": "3 日",
|
||||
"date.day.30": "30 日",
|
||||
"date.day.31": "31 日",
|
||||
"date.day.4": "4 日",
|
||||
"date.day.5": "5 日",
|
||||
"date.day.6": "6 日",
|
||||
"date.day.7": "7 日",
|
||||
"date.day.8": "8 日",
|
||||
"date.day.9": "9 日",
|
||||
"date.month.1": "1月",
|
||||
"date.month.10": "10月",
|
||||
"date.month.11": "11月",
|
||||
@@ -311,6 +311,7 @@
|
||||
"loading.default": "載入中",
|
||||
"loading.heic.converting": "正在轉換 HEIC/HEIF 圖像格式...",
|
||||
"loading.heic.main": "HEIC",
|
||||
"loading.queue.waiting": "正在排隊等待轉換...",
|
||||
"loading.webgl.building": "正在建置高品質紋理...",
|
||||
"loading.webgl.main": "WebGL 紋理載入",
|
||||
"minimap.loading": "載入地圖中...",
|
||||
@@ -369,4 +370,4 @@
|
||||
"video.conversion.webcodecs.not.supported": "此瀏覽器不支援 WebCodecs",
|
||||
"video.format.mov.not.supported": "瀏覽器不支援 MOV 格式,需要轉換",
|
||||
"video.format.mov.supported": "瀏覽器原生支援 MOV 格式,跳過轉換"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user