feat: Integrate internationalization support for enhanced user experience

- Add I18n class for language management and translations.
- Update settings tab and image handler to utilize internationalization for labels and messages.
- Modify event handlers to support localized menu titles.
- Ensure language settings are configurable and persist across sessions.
This commit is contained in:
fantasy-ke 2025-12-18 12:57:22 +08:00
parent 39ff979c1b
commit 601a926858
7 changed files with 420 additions and 67 deletions

View file

@ -4,20 +4,25 @@ import { UploadService } from './src/upload/uploadService';
import { ImageHandler } from './src/upload/imageHandler';
import { EventHandlers } from './src/events/eventHandlers';
import { CFImageBedSettingTab } from './src/settings/settingsTab';
import { I18n } from './src/utils/i18n';
export default class CFImageBedPlugin extends Plugin {
settings: CFImageBedSettings;
private uploadService: UploadService;
private imageHandler: ImageHandler;
private eventHandlers: EventHandlers;
private i18n: I18n;
async onload() {
await this.loadSettings();
// 初始化i18n
this.i18n = new I18n(this.settings.language || 'zh');
// 初始化服务
this.uploadService = new UploadService(this.settings);
this.imageHandler = new ImageHandler(this.app, this.uploadService, () => this.settings);
this.eventHandlers = new EventHandlers(this.imageHandler);
this.imageHandler = new ImageHandler(this.app, this.uploadService, () => this.settings, this.i18n);
this.eventHandlers = new EventHandlers(this.imageHandler, this.i18n);
// 注册事件处理器
this.eventHandlers.registerDragAndDropEvents(this);

View file

@ -1,8 +1,12 @@
import { Plugin, Menu, Editor, MarkdownView } from 'obsidian';
import { ImageHandler } from '../upload/imageHandler';
import { I18n } from '../utils/i18n';
export class EventHandlers {
constructor(private imageHandler: ImageHandler) {}
constructor(
private imageHandler: ImageHandler,
private i18n: I18n
) {}
registerDragAndDropEvents(plugin: Plugin): void {
// 添加拖拽上传功能
@ -57,7 +61,7 @@ export class EventHandlers {
plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownView) => {
menu.addItem((item) => {
item
.setTitle('Upload image to CF ImageBed')
.setTitle(this.i18n.t('menu.uploadImage'))
.setIcon('upload')
.onClick(() => {
this.imageHandler.selectAndUploadImage();

View file

@ -1,12 +1,15 @@
import { App, PluginSettingTab, Setting } from 'obsidian';
import CFImageBedPlugin from '../../main';
import { I18n } from '../utils/i18n';
export class CFImageBedSettingTab extends PluginSettingTab {
plugin: CFImageBedPlugin;
private i18n: I18n;
constructor(app: App, plugin: CFImageBedPlugin) {
super(app, plugin);
this.plugin = plugin;
this.i18n = new I18n(plugin.settings.language || 'zh');
}
display(): void {
@ -14,20 +17,39 @@ export class CFImageBedSettingTab extends PluginSettingTab {
containerEl.empty();
// 更新语言
this.i18n.setLanguage(this.plugin.settings.language || 'zh');
// 添加插件专用的CSS类名限制样式作用域
containerEl.addClass('cf-imagebed-settings');
new Setting(containerEl).setName('CF ImageBed settings').setHeading();
new Setting(containerEl).setName(this.i18n.t('settings.title')).setHeading();
// 语言设置(在顶部)
new Setting(containerEl)
.setName(this.i18n.t('settings.language.name'))
.setDesc(this.i18n.t('settings.language.desc'))
.addDropdown(dropdown => dropdown
.addOption('zh', '中文')
.addOption('en', 'English')
.setValue(this.plugin.settings.language || 'zh')
.onChange(async (value: 'zh' | 'en') => {
this.plugin.settings.language = value;
await this.plugin.saveSettings();
this.i18n.setLanguage(value);
// 重新渲染设置界面
this.display();
}));
// 创建选项卡容器
const tabContainer = containerEl.createDiv('cf-imagebed-tabs');
const tabContent = containerEl.createDiv('cf-imagebed-tab-content');
// 创建选项卡按钮
const basicTab = tabContainer.createEl('button', { text: '基础设置', cls: 'cf-tab-button active' });
const advancedTab = tabContainer.createEl('button', { text: '高级设置', cls: 'cf-tab-button' });
const userTab = tabContainer.createEl('button', { text: '用户体验', cls: 'cf-tab-button' });
const backupTab = tabContainer.createEl('button', { text: '备份设置', cls: 'cf-tab-button' });
const basicTab = tabContainer.createEl('button', { text: this.i18n.t('settings.tabs.basic'), cls: 'cf-tab-button active' });
const advancedTab = tabContainer.createEl('button', { text: this.i18n.t('settings.tabs.advanced'), cls: 'cf-tab-button' });
const userTab = tabContainer.createEl('button', { text: this.i18n.t('settings.tabs.userExperience'), cls: 'cf-tab-button' });
const backupTab = tabContainer.createEl('button', { text: this.i18n.t('settings.tabs.backup'), cls: 'cf-tab-button' });
// 创建选项卡内容区域
const basicContent = tabContent.createDiv('cf-tab-panel active');
@ -67,8 +89,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
private createBasicSettings(container: HTMLElement): void {
// API URL 设置
new Setting(container)
.setName('API URL')
.setDesc('CloudFlare ImgBed API address (e.g., https://your.domain)')
.setName(this.i18n.t('settings.basic.apiUrl.name'))
.setDesc(this.i18n.t('settings.basic.apiUrl.desc'))
.addText(text => text
.setPlaceholder('https://your.domain')
.setValue(this.plugin.settings.apiUrl)
@ -79,8 +101,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 认证码设置
new Setting(container)
.setName('Auth code')
.setDesc('Upload authentication code')
.setName(this.i18n.t('settings.basic.authCode.name'))
.setDesc(this.i18n.t('settings.basic.authCode.desc'))
.addText(text => text
.setPlaceholder('your_authCode')
.setValue(this.plugin.settings.authCode)
@ -91,8 +113,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 上传渠道设置
new Setting(container)
.setName('Upload channel')
.setDesc('Select upload channel')
.setName(this.i18n.t('settings.basic.uploadChannel.name'))
.setDesc(this.i18n.t('settings.basic.uploadChannel.desc'))
.addDropdown(dropdown => dropdown
.addOption('telegram', 'Telegram')
.addOption('cfr2', 'CloudFlare R2')
@ -105,8 +127,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 文件命名方式设置
new Setting(container)
.setName('File naming method')
.setDesc('Select file naming method')
.setName(this.i18n.t('settings.basic.uploadNameType.name'))
.setDesc(this.i18n.t('settings.basic.uploadNameType.desc'))
.addDropdown(dropdown => dropdown
.addOption('default', '默认前缀_原名命名')
.addOption('index', '仅前缀命名')
@ -120,8 +142,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 返回格式设置
new Setting(container)
.setName('Return link format')
.setDesc('Select return link format')
.setName(this.i18n.t('settings.basic.returnFormat.name'))
.setDesc(this.i18n.t('settings.basic.returnFormat.desc'))
.addDropdown(dropdown => dropdown
.addOption('default', '默认格式 /file/id')
.addOption('full', '完整链接格式')
@ -133,8 +155,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 上传目录设置
new Setting(container)
.setName('Upload folder')
.setDesc('Upload folder, use relative path (e.g., img/test)')
.setName(this.i18n.t('settings.basic.uploadFolder.name'))
.setDesc(this.i18n.t('settings.basic.uploadFolder.desc'))
.addText(text => text
.setPlaceholder('img/test')
.setValue(this.plugin.settings.uploadFolder)
@ -145,8 +167,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 服务端压缩设置
new Setting(container)
.setName('Server compression')
.setDesc('Enable server-side compression (only for Telegram channel image files)')
.setName(this.i18n.t('settings.basic.serverCompress.name'))
.setDesc(this.i18n.t('settings.basic.serverCompress.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.serverCompress)
.onChange(async (value) => {
@ -156,8 +178,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 自动重试设置
new Setting(container)
.setName('Auto retry')
.setDesc('Automatically switch channels and retry on failure')
.setName(this.i18n.t('settings.basic.autoRetry.name'))
.setDesc(this.i18n.t('settings.basic.autoRetry.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.autoRetry)
.onChange(async (value) => {
@ -169,8 +191,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
private createAdvancedSettings(container: HTMLElement): void {
// 文件大小限制
new Setting(container)
.setName('Maximum file size')
.setDesc('Set maximum size for uploaded files (MB)')
.setName(this.i18n.t('settings.advanced.maxFileSize.name'))
.setDesc(this.i18n.t('settings.advanced.maxFileSize.desc'))
.addSlider(slider => slider
.setLimits(1, 50, 1)
.setValue(this.plugin.settings.maxFileSize)
@ -182,8 +204,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 允许的文件类型
new Setting(container)
.setName('Allowed file types')
.setDesc('Set allowed file types for upload (comma-separated)')
.setName(this.i18n.t('settings.advanced.allowedFileTypes.name'))
.setDesc(this.i18n.t('settings.advanced.allowedFileTypes.desc'))
.addText(text => text
.setPlaceholder('jpg,jpeg,png,gif,webp,bmp')
.setValue(this.plugin.settings.allowedFileTypes.join(','))
@ -194,8 +216,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 水印设置
new Setting(container)
.setName('Enable watermark')
.setDesc('Add watermark to uploaded images')
.setName(this.i18n.t('settings.advanced.enableWatermark.name'))
.setDesc(this.i18n.t('settings.advanced.enableWatermark.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableWatermark)
.onChange(async (value) => {
@ -205,7 +227,12 @@ export class CFImageBedSettingTab extends PluginSettingTab {
const fields = container.querySelectorAll('input, select');
fields.forEach((el) => {
const label = (el.closest('.setting-item')?.querySelector('.setting-item-name')?.textContent || '').trim();
const dependent = ['Watermark text', 'Watermark position', 'Watermark font size', 'Watermark opacity'];
const dependent = [
this.i18n.t('settings.advanced.watermarkText.name'),
this.i18n.t('settings.advanced.watermarkPosition.name'),
this.i18n.t('settings.advanced.watermarkSize.name'),
this.i18n.t('settings.advanced.watermarkOpacity.name')
];
if (dependent.some(d => label.includes(d))) {
(el as HTMLInputElement | HTMLSelectElement).disabled = !value;
}
@ -214,8 +241,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 水印文字
new Setting(container)
.setName('Watermark text')
.setDesc('Set watermark text content')
.setName(this.i18n.t('settings.advanced.watermarkText.name'))
.setDesc(this.i18n.t('settings.advanced.watermarkText.desc'))
.addText(text => text
.setPlaceholder('水印文字')
.setValue(this.plugin.settings.watermarkText)
@ -227,8 +254,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 水印位置
new Setting(container)
.setName('Watermark position')
.setDesc('Set watermark position in image')
.setName(this.i18n.t('settings.advanced.watermarkPosition.name'))
.setDesc(this.i18n.t('settings.advanced.watermarkPosition.desc'))
.addDropdown(dropdown => dropdown
.addOption('top-left', '左上角')
.addOption('top-right', '右上角')
@ -244,8 +271,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 水印字体大小
new Setting(container)
.setName('Watermark font size')
.setDesc('Set watermark text font size (pixels)')
.setName(this.i18n.t('settings.advanced.watermarkSize.name'))
.setDesc(this.i18n.t('settings.advanced.watermarkSize.desc'))
.addSlider(slider => slider
.setLimits(12, 72, 2)
.setValue(this.plugin.settings.watermarkSize)
@ -258,8 +285,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 水印透明度
new Setting(container)
.setName('Watermark opacity')
.setDesc('Set watermark opacity (0-1)')
.setName(this.i18n.t('settings.advanced.watermarkOpacity.name'))
.setDesc(this.i18n.t('settings.advanced.watermarkOpacity.desc'))
.addSlider(slider => slider
.setLimits(0.1, 1, 0.1)
.setValue(this.plugin.settings.watermarkOpacity)
@ -272,8 +299,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 客户端压缩设置
new Setting(container)
.setName('Enable client compression')
.setDesc('Automatically compress images before upload to reduce file size')
.setName(this.i18n.t('settings.advanced.enableClientCompress.name'))
.setDesc(this.i18n.t('settings.advanced.enableClientCompress.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableClientCompress)
.onChange(async (value) => {
@ -283,7 +310,10 @@ export class CFImageBedSettingTab extends PluginSettingTab {
const fields = container.querySelectorAll('input');
fields.forEach((el) => {
const label = (el.closest('.setting-item')?.querySelector('.setting-item-name')?.textContent || '').trim();
const dependent = ['Compression threshold', 'Target size'];
const dependent = [
this.i18n.t('settings.advanced.compressThreshold.name'),
this.i18n.t('settings.advanced.targetSize.name')
];
if (dependent.some(d => label.includes(d))) {
(el as HTMLInputElement).disabled = !value;
}
@ -292,8 +322,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 压缩阈值
new Setting(container)
.setName('Compression threshold')
.setDesc('Set image size threshold, files exceeding this will be automatically compressed (MB)')
.setName(this.i18n.t('settings.advanced.compressThreshold.name'))
.setDesc(this.i18n.t('settings.advanced.compressThreshold.desc'))
.addSlider(slider => slider
.setLimits(0.5, 10, 0.5)
.setValue(this.plugin.settings.compressThreshold)
@ -306,8 +336,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 期望大小
new Setting(container)
.setName('Target size')
.setDesc('Set expected size for compressed images (MB)')
.setName(this.i18n.t('settings.advanced.targetSize.name'))
.setDesc(this.i18n.t('settings.advanced.targetSize.desc'))
.addSlider(slider => slider
.setLimits(0.1, 5, 0.1)
.setValue(this.plugin.settings.targetSize)
@ -322,8 +352,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
private createUserExperienceSettings(container: HTMLElement): void {
// 显示上传进度
new Setting(container)
.setName('Show upload progress')
.setDesc('Show progress information during upload')
.setName(this.i18n.t('settings.userExperience.showUploadProgress.name'))
.setDesc(this.i18n.t('settings.userExperience.showUploadProgress.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showUploadProgress)
.onChange(async (value) => {
@ -333,8 +363,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 显示成功通知
new Setting(container)
.setName('Show success notification')
.setDesc('Show notification message on successful upload')
.setName(this.i18n.t('settings.userExperience.showSuccessNotification.name'))
.setDesc(this.i18n.t('settings.userExperience.showSuccessNotification.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showSuccessNotification)
.onChange(async (value) => {
@ -344,8 +374,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 显示错误通知
new Setting(container)
.setName('Show error notification')
.setDesc('Show error message when upload fails')
.setName(this.i18n.t('settings.userExperience.showErrorNotification.name'))
.setDesc(this.i18n.t('settings.userExperience.showErrorNotification.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showErrorNotification)
.onChange(async (value) => {
@ -355,8 +385,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 通知持续时间
new Setting(container)
.setName('Notification duration')
.setDesc('Set duration for notification display (seconds)')
.setName(this.i18n.t('settings.userExperience.notificationDuration.name'))
.setDesc(this.i18n.t('settings.userExperience.notificationDuration.desc'))
.addSlider(slider => slider
.setLimits(1, 10, 1)
.setValue(this.plugin.settings.notificationDuration)
@ -372,8 +402,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
private createBackupSettings(container: HTMLElement): void {
// 启用本地备份
new Setting(container)
.setName('Enable local backup')
.setDesc('Save a local backup while uploading to cloud')
.setName(this.i18n.t('settings.backup.enableLocalBackup.name'))
.setDesc(this.i18n.t('settings.backup.enableLocalBackup.desc'))
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableLocalBackup)
.onChange(async (value) => {
@ -386,8 +416,8 @@ export class CFImageBedSettingTab extends PluginSettingTab {
// 备份路径
new Setting(container)
.setName('Backup path')
.setDesc('Set local backup storage path (relative to vault root)')
.setName(this.i18n.t('settings.backup.backupPath.name'))
.setDesc(this.i18n.t('settings.backup.backupPath.desc'))
.addText(text => text
.setPlaceholder('attachments/backup')
.setValue(this.plugin.settings.backupPath)

View file

@ -32,6 +32,9 @@ export interface CFImageBedSettings {
// 备份配置
enableLocalBackup: boolean;
backupPath: string;
// 语言配置
language: 'zh' | 'en';
}
export const DEFAULT_SETTINGS: CFImageBedSettings = {
@ -68,5 +71,8 @@ export const DEFAULT_SETTINGS: CFImageBedSettings = {
// 备份配置
enableLocalBackup: false,
backupPath: 'attachments/backup',
// 语言配置
language: 'zh',
};

View file

@ -1,12 +1,14 @@
import { App, MarkdownView, Notice, Platform } from 'obsidian';
import { CFImageBedSettings } from '../types';
import { UploadService } from './uploadService';
import { I18n } from '../utils/i18n';
export class ImageHandler {
constructor(
private app: App,
private uploadService: UploadService,
private getSettings?: () => CFImageBedSettings
private getSettings?: () => CFImageBedSettings,
private i18n?: I18n
) {}
async uploadImageFromFile(file: File, deleteLocal: boolean = false): Promise<void> {
@ -106,22 +108,22 @@ export class ImageHandler {
dialog.className = 'cf-imagebed-dialog';
const title = document.createElement('h3');
title.textContent = 'Select image source';
title.textContent = this.i18n?.t('mobile.selectSource') || 'Select image source';
title.className = 'cf-imagebed-dialog-title';
const buttonContainer = document.createElement('div');
buttonContainer.className = 'cf-imagebed-button-container';
const cameraBtn = document.createElement('button');
cameraBtn.textContent = '📷 Take photo';
cameraBtn.textContent = this.i18n?.t('mobile.takePhoto') || '📷 Take photo';
cameraBtn.className = 'cf-imagebed-camera-btn';
const galleryBtn = document.createElement('button');
galleryBtn.textContent = '🖼️ Select from gallery';
galleryBtn.textContent = this.i18n?.t('mobile.selectFromGallery') || '🖼️ Select from gallery';
galleryBtn.className = 'cf-imagebed-gallery-btn';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.textContent = this.i18n?.t('mobile.cancel') || 'Cancel';
cancelBtn.className = 'cf-imagebed-cancel-btn';
// 相机拍照

307
src/utils/i18n.ts Normal file
View file

@ -0,0 +1,307 @@
export type Language = 'zh' | 'en';
export interface Translations {
[key: string]: string | Translations;
}
const translations: Record<Language, Translations> = {
zh: {
settings: {
title: 'CF ImageBed 设置',
tabs: {
basic: '基础设置',
advanced: '高级设置',
userExperience: '用户体验',
backup: '备份设置'
},
basic: {
apiUrl: {
name: 'API URL',
desc: 'CloudFlare ImgBed 的 API 地址例如https://your.domain'
},
authCode: {
name: '认证码',
desc: '上传认证码'
},
uploadChannel: {
name: '上传渠道',
desc: '选择上传渠道'
},
uploadNameType: {
name: '文件命名方式',
desc: '选择文件命名方式'
},
returnFormat: {
name: '返回链接格式',
desc: '选择返回链接格式'
},
uploadFolder: {
name: '上传目录',
desc: '上传目录用相对路径表示例如img/test'
},
serverCompress: {
name: '服务端压缩',
desc: '启用服务端压缩(仅针对 Telegram 渠道的图片文件)'
},
autoRetry: {
name: '自动重试',
desc: '失败时自动切换渠道重试'
}
},
advanced: {
maxFileSize: {
name: '最大文件大小',
desc: '设置上传文件的最大大小MB'
},
allowedFileTypes: {
name: '允许的文件类型',
desc: '设置允许上传的文件类型(用逗号分隔)'
},
enableWatermark: {
name: '启用水印',
desc: '为上传的图片添加水印'
},
watermarkText: {
name: '水印文字',
desc: '设置水印文字内容'
},
watermarkPosition: {
name: '水印位置',
desc: '设置水印在图片中的位置'
},
watermarkSize: {
name: '水印字体大小',
desc: '设置水印文字的字体大小(像素)'
},
watermarkOpacity: {
name: '水印透明度',
desc: '设置水印的透明度0-1'
},
enableClientCompress: {
name: '启用客户端压缩',
desc: '在上传前自动压缩图片以减少文件大小'
},
compressThreshold: {
name: '压缩阈值',
desc: '设置图片大小阈值超过此值将自动压缩MB'
},
targetSize: {
name: '期望大小',
desc: '设置压缩后图片大小期望值MB'
}
},
userExperience: {
showUploadProgress: {
name: '显示上传提示',
desc: '在上传过程中显示提示信息'
},
showSuccessNotification: {
name: '显示成功通知',
desc: '上传成功后显示通知消息'
},
showErrorNotification: {
name: '显示错误通知',
desc: '上传失败时显示错误消息'
},
notificationDuration: {
name: '通知持续时间',
desc: '设置通知消息显示的持续时间(秒)'
}
},
backup: {
enableLocalBackup: {
name: '启用本地备份',
desc: '在上传到云端的同时,在本地保存一份备份'
},
backupPath: {
name: '备份路径',
desc: '设置本地备份的存储路径(相对于库根目录)'
}
},
language: {
name: '语言设置',
desc: '选择界面显示语言'
}
},
menu: {
uploadImage: '上传图片到 CF ImageBed'
},
mobile: {
selectSource: '选择图片来源',
takePhoto: '📷 拍照',
selectFromGallery: '🖼️ 从相册选择',
cancel: '取消'
}
},
en: {
settings: {
title: 'CF ImageBed settings',
tabs: {
basic: 'Basic settings',
advanced: 'Advanced settings',
userExperience: 'User experience',
backup: 'Backup settings'
},
basic: {
apiUrl: {
name: 'API URL',
desc: 'CloudFlare ImgBed API address (e.g., https://your.domain)'
},
authCode: {
name: 'Auth code',
desc: 'Upload authentication code'
},
uploadChannel: {
name: 'Upload channel',
desc: 'Select upload channel'
},
uploadNameType: {
name: 'File naming method',
desc: 'Select file naming method'
},
returnFormat: {
name: 'Return link format',
desc: 'Select return link format'
},
uploadFolder: {
name: 'Upload folder',
desc: 'Upload folder, use relative path (e.g., img/test)'
},
serverCompress: {
name: 'Server compression',
desc: 'Enable server-side compression (only for Telegram channel image files)'
},
autoRetry: {
name: 'Auto retry',
desc: 'Automatically switch channels and retry on failure'
}
},
advanced: {
maxFileSize: {
name: 'Maximum file size',
desc: 'Set maximum size for uploaded files (MB)'
},
allowedFileTypes: {
name: 'Allowed file types',
desc: 'Set allowed file types for upload (comma-separated)'
},
enableWatermark: {
name: 'Enable watermark',
desc: 'Add watermark to uploaded images'
},
watermarkText: {
name: 'Watermark text',
desc: 'Set watermark text content'
},
watermarkPosition: {
name: 'Watermark position',
desc: 'Set watermark position in image'
},
watermarkSize: {
name: 'Watermark font size',
desc: 'Set watermark text font size (pixels)'
},
watermarkOpacity: {
name: 'Watermark opacity',
desc: 'Set watermark opacity (0-1)'
},
enableClientCompress: {
name: 'Enable client compression',
desc: 'Automatically compress images before upload to reduce file size'
},
compressThreshold: {
name: 'Compression threshold',
desc: 'Set image size threshold, files exceeding this will be automatically compressed (MB)'
},
targetSize: {
name: 'Target size',
desc: 'Set expected size for compressed images (MB)'
}
},
userExperience: {
showUploadProgress: {
name: 'Show upload progress',
desc: 'Show progress information during upload'
},
showSuccessNotification: {
name: 'Show success notification',
desc: 'Show notification message on successful upload'
},
showErrorNotification: {
name: 'Show error notification',
desc: 'Show error message when upload fails'
},
notificationDuration: {
name: 'Notification duration',
desc: 'Set duration for notification display (seconds)'
}
},
backup: {
enableLocalBackup: {
name: 'Enable local backup',
desc: 'Save a local backup while uploading to cloud'
},
backupPath: {
name: 'Backup path',
desc: 'Set local backup storage path (relative to vault root)'
}
},
language: {
name: 'Language',
desc: 'Select interface display language'
}
},
menu: {
uploadImage: 'Upload image to CF ImageBed'
},
mobile: {
selectSource: 'Select image source',
takePhoto: '📷 Take photo',
selectFromGallery: '🖼️ Select from gallery',
cancel: 'Cancel'
}
}
};
export class I18n {
private currentLanguage: Language;
constructor(language: Language = 'zh') {
this.currentLanguage = language;
}
setLanguage(language: Language): void {
this.currentLanguage = language;
}
getLanguage(): Language {
return this.currentLanguage;
}
t(key: string): string {
const keys = key.split('.');
let value: any = translations[this.currentLanguage];
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
// Fallback to English if key not found
value = translations.en;
for (const k2 of keys) {
if (value && typeof value === 'object' && k2 in value) {
value = value[k2];
} else {
return key; // Return key if translation not found
}
}
break;
}
}
return typeof value === 'string' ? value : key;
}
}
export const i18n = new I18n('zh');

View file

@ -1,10 +1,8 @@
/*
This CSS file will be included with your plugin, and
available in the app when your plugin is enabled.
If your plugin does not need CSS, delete this file.
*/
/* CF ImageBed 插件专用样式 - 限制作用域避免影响全局 */
@ -280,4 +278,5 @@ If your plugin does not need CSS, delete this file.
.cf-imagebed-cancel-btn:hover {
background-color: #f8f9fa;
border-color: #dee2e6;
}
}