From f27ada6a4e9c3dca86be5f27ce8bd22fa4da8509 Mon Sep 17 00:00:00 2001 From: wood chen Date: Mon, 3 Feb 2025 23:07:04 +0800 Subject: [PATCH] Add internationalization (i18n) support - Introduced i18n framework with translation support - Updated config, main, and styles files to use translation keys - Implemented dynamic locale setting based on Obsidian's language - Replaced hardcoded Chinese strings with translatable keys - Added locale change event listener to update translations dynamically --- src/config.ts | 13 ++-- src/i18n/en.ts | 37 +++++++++++ src/i18n/index.ts | 30 +++++++++ src/i18n/zh-CN.ts | 37 +++++++++++ src/main.ts | 155 +++++++++++++++++++++++++--------------------- styles.css | 36 +++++------ 6 files changed, 212 insertions(+), 96 deletions(-) create mode 100644 src/i18n/en.ts create mode 100644 src/i18n/index.ts create mode 100644 src/i18n/zh-CN.ts diff --git a/src/config.ts b/src/config.ts index 0cf2a32..ed74568 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import { PluginSettingTab, Setting, App } from 'obsidian'; import DiscourseSyncPlugin from './main'; +import { t } from './i18n'; export interface DiscourseSyncSettings { baseUrl: string; @@ -28,8 +29,8 @@ export class DiscourseSyncSettingsTab extends PluginSettingTab { containerEl.empty(); new Setting(containerEl) - .setName("论坛地址") - .setDesc("Discourse 论坛的网址") + .setName(t('FORUM_URL')) + .setDesc(t('FORUM_URL_DESC')) .addText((text) => text .setPlaceholder("https://forum.example.com") @@ -41,8 +42,8 @@ export class DiscourseSyncSettingsTab extends PluginSettingTab { ); new Setting(containerEl) - .setName("API 密钥") - .setDesc("在'/admin/api/keys'中创建的 API 密钥") + .setName(t('API_KEY')) + .setDesc(t('API_KEY_DESC')) .addText((text) => text .setPlaceholder("api_key") @@ -54,8 +55,8 @@ export class DiscourseSyncSettingsTab extends PluginSettingTab { ); new Setting(containerEl) - .setName("用户名") - .setDesc("Discourse 用户名") + .setName(t('USERNAME')) + .setDesc(t('USERNAME_DESC')) .addText((text) => text .setPlaceholder("username") diff --git a/src/i18n/en.ts b/src/i18n/en.ts new file mode 100644 index 0000000..58c05dd --- /dev/null +++ b/src/i18n/en.ts @@ -0,0 +1,37 @@ +export default { + // Settings page + 'FORUM_URL': 'Forum URL', + 'FORUM_URL_DESC': 'The URL of your Discourse forum', + 'API_KEY': 'API Key', + 'API_KEY_DESC': "API key created in '/admin/api/keys'", + 'USERNAME': 'Username', + 'USERNAME_DESC': 'Your Discourse username', + + // Publish page + 'PUBLISH_TO_DISCOURSE': 'Publish to Discourse', + 'UPDATE_POST': 'Update Post', + 'CATEGORY': 'Category', + 'TAGS': 'Tags', + 'ENTER_TAG': 'Enter tag name (press Enter to add)', + 'ENTER_TAG_WITH_CREATE': 'Enter tag name (can create new tags)', + 'PUBLISHING': 'Publishing...', + 'UPDATING': 'Updating...', + 'PUBLISH': 'Publish', + 'UPDATE': 'Update', + 'RETRY': 'Retry', + + // Success messages + 'PUBLISH_SUCCESS': '✓ Published successfully!', + 'UPDATE_SUCCESS': '✓ Updated successfully!', + + // Error messages + 'PUBLISH_FAILED': 'Publish failed', + 'UPDATE_FAILED': 'Update failed', + 'PUBLISH_ERROR': 'Publish error', + 'UPDATE_ERROR': 'Update error', + 'PERMISSION_ERROR': 'Insufficient permissions, can only use existing tags', + 'UNKNOWN_ERROR': 'Unknown error', + 'TRY_AGAIN': 'Please try again', + 'POST_ID_ERROR': 'Published successfully but failed to get post ID', + 'SAVE_POST_ID_ERROR': 'Published successfully but failed to save post ID', +} \ No newline at end of file diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..abe8c76 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,30 @@ +import { moment } from 'obsidian'; +import zhCN from './zh-CN'; +import en from './en'; + +const localeMap: { [key: string]: any } = { + 'zh': zhCN, + 'zh-cn': zhCN, + 'en': en, + 'en-us': en, + 'en-gb': en, +}; + +let currentLocale = 'en'; + +export function setLocale(locale: string) { + const normalizedLocale = locale.toLowerCase(); + if (localeMap[normalizedLocale]) { + currentLocale = normalizedLocale; + moment.locale(normalizedLocale); + } +} + +export function getCurrentLocale(): string { + return currentLocale; +} + +export function t(key: string): string { + const translations = localeMap[currentLocale] || localeMap['en']; + return translations[key] || localeMap['en'][key] || key; +} \ No newline at end of file diff --git a/src/i18n/zh-CN.ts b/src/i18n/zh-CN.ts new file mode 100644 index 0000000..6f1d735 --- /dev/null +++ b/src/i18n/zh-CN.ts @@ -0,0 +1,37 @@ +export default { + // 设置页面 + 'FORUM_URL': '论坛地址', + 'FORUM_URL_DESC': 'Discourse 论坛的网址', + 'API_KEY': 'API 密钥', + 'API_KEY_DESC': "在'/admin/api/keys'中创建的 API 密钥", + 'USERNAME': '用户名', + 'USERNAME_DESC': 'Discourse 用户名', + + // 发布页面 + 'PUBLISH_TO_DISCOURSE': '发布到 Discourse', + 'UPDATE_POST': '更新帖子', + 'CATEGORY': '分类', + 'TAGS': '标签', + 'ENTER_TAG': '输入标签名称(回车添加)', + 'ENTER_TAG_WITH_CREATE': '输入标签名称(可创建新标签)', + 'PUBLISHING': '发布中...', + 'UPDATING': '更新中...', + 'PUBLISH': '发布', + 'UPDATE': '更新', + 'RETRY': '重试', + + // 成功提示 + 'PUBLISH_SUCCESS': '✓ 发布成功!', + 'UPDATE_SUCCESS': '✓ 更新成功!', + + // 错误提示 + 'PUBLISH_FAILED': '发布失败', + 'UPDATE_FAILED': '更新失败', + 'PUBLISH_ERROR': '发布出错', + 'UPDATE_ERROR': '更新出错', + 'PERMISSION_ERROR': '权限不足,只能使用已有的标签', + 'UNKNOWN_ERROR': '未知错误', + 'TRY_AGAIN': '请重试', + 'POST_ID_ERROR': '发布成功但无法获取帖子ID', + 'SAVE_POST_ID_ERROR': '发布成功但无法保存帖子ID', +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 9e65a1a..1e21073 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,15 +1,26 @@ -import { App, Menu, MenuItem, Plugin, Modal, requestUrl, TFile } from 'obsidian'; +import { App, Menu, MenuItem, Plugin, Modal, requestUrl, TFile, moment } from 'obsidian'; import { DEFAULT_SETTINGS, DiscourseSyncSettings, DiscourseSyncSettingsTab } from './config'; import * as yaml from 'yaml'; +import { t, setLocale } from './i18n'; export default class DiscourseSyncPlugin extends Plugin { settings: DiscourseSyncSettings; activeFile: { name: string; content: string; - postId?: number; // 添加帖子ID字段 + postId?: number; // Post ID field }; async onload() { + // Set locale based on Obsidian's language setting + setLocale(moment.locale()); + + // Update locale when Obsidian's language changes + this.registerEvent( + this.app.workspace.on('window-open', () => { + setLocale(moment.locale()); + }) + ); + await this.loadSettings(); this.addSettingTab(new DiscourseSyncSettingsTab(this.app, this)); this.registerEvent( @@ -20,7 +31,7 @@ export default class DiscourseSyncPlugin extends Plugin { this.addCommand({ id: "category-modal", - name: "发布到 Discourse", + name: t('PUBLISH_TO_DISCOURSE'), callback: () => { this.openCategoryModal(); }, @@ -118,11 +129,11 @@ export default class DiscourseSyncPlugin extends Plugin { } let content = this.activeFile.content; - // 检查是否包含帖子ID的frontmatter + // Check if frontmatter contains post ID const frontMatter = this.getFrontMatter(content); const postId = frontMatter?.discourse_post_id; - // 过滤掉笔记属性部分 + // Filter out note properties section content = content.replace(/^---[\s\S]*?---\n/, ''); const imageReferences = this.extractImageReferences(content); @@ -162,20 +173,20 @@ export default class DiscourseSyncPlugin extends Plugin { if (response.status === 200) { if (!isUpdate) { try { - // 获取新帖子的ID + // Get new post ID const responseData = response.json; if (responseData && responseData.id) { await this.updateFrontMatter(responseData.id); } else { return { message: "Error", - error: "发布成功但无法获取帖子ID" + error: t('POST_ID_ERROR') }; } } catch (error) { return { message: "Error", - error: "发布成功但无法保存帖子ID" + error: t('SAVE_POST_ID_ERROR') }; } } @@ -198,20 +209,20 @@ export default class DiscourseSyncPlugin extends Plugin { } catch (parseError) { return { message: "Error", - error: `${isUpdate ? '更新' : '发布'}失败 (${response.status})` + error: `${isUpdate ? t('UPDATE_FAILED') : t('PUBLISH_FAILED')} (${response.status})` }; } } } catch (error) { return { message: "Error", - error: `${isUpdate ? '更新' : '发布'}失败: ${error.message || '未知错误'}` + error: `${isUpdate ? t('UPDATE_FAILED') : t('PUBLISH_FAILED')}: ${error.message || t('UNKNOWN_ERROR')}` }; } - return { message: "Error", error: `${isUpdate ? '更新' : '发布'}失败,请重试` }; + return { message: "Error", error: `${isUpdate ? t('UPDATE_FAILED') : t('PUBLISH_FAILED')}, ${t('TRY_AGAIN')}` }; } - // 获取frontmatter数据 + // Get frontmatter data private getFrontMatter(content: string): any { const fmMatch = content.match(/^---\n([\s\S]*?)\n---/); if (fmMatch) { @@ -224,10 +235,10 @@ export default class DiscourseSyncPlugin extends Plugin { return null; } - // 更新frontmatter,添加帖子ID + // Update frontmatter, add post ID private async updateFrontMatter(postId: number) { try { - // 获取当前活动文件 + // Get current active file const activeFile = this.app.workspace.getActiveFile(); if (!activeFile) { return; @@ -238,11 +249,11 @@ export default class DiscourseSyncPlugin extends Plugin { let newContent: string; if (fm) { - // 更新现有的frontmatter + // Update existing frontmatter const updatedFm = { ...fm, discourse_post_id: postId }; newContent = content.replace(/^---\n[\s\S]*?\n---\n/, `---\n${yaml.stringify(updatedFm)}---\n`); } else { - // 添加新的frontmatter + // Add new frontmatter const newFm = { discourse_post_id: postId }; newContent = `---\n${yaml.stringify(newFm)}---\n${content}`; } @@ -251,7 +262,7 @@ export default class DiscourseSyncPlugin extends Plugin { } catch (error) { return { message: "Error", - error: "更新失败" + error: t('UPDATE_FAILED') }; } } @@ -309,9 +320,9 @@ export default class DiscourseSyncPlugin extends Plugin { if (response.status === 200) { const data = response.json; - // 获取用户权限信息 + // Get user permissions const canCreateTags = await this.checkCanCreateTags(); - // Discourse 返回的 tags 列表在 tags 数组中 + // Tags list returned by Discourse is in the tags array return data.tags.map((tag: any) => ({ name: tag.name, canCreate: canCreateTags @@ -323,7 +334,7 @@ export default class DiscourseSyncPlugin extends Plugin { } } - // 检查用户是否有创建标签的权限 + // Check if user has permission to create tags private async checkCanCreateTags(): Promise { try { const url = `${this.settings.baseUrl}/u/${this.settings.disUser}.json`; @@ -342,7 +353,7 @@ export default class DiscourseSyncPlugin extends Plugin { if (response.status === 200) { const data = response.json; - // 检查用户的 trust_level + // Check user's trust_level return data.user.trust_level >= 3; } return false; @@ -353,7 +364,7 @@ export default class DiscourseSyncPlugin extends Plugin { registerDirMenu(menu: Menu, file: TFile) { const syncDiscourse = (item: MenuItem) => { - item.setTitle("发布到 Discourse"); + item.setTitle(t('PUBLISH_TO_DISCOURSE')); item.onClick(async () => { const content = await this.app.vault.read(file); const fm = this.getFrontMatter(content); @@ -441,23 +452,23 @@ export class SelectCategoryModal extends Modal { } onOpen() { - // 添加模态框基础样式 + // Add modal base style this.modalEl.addClass('mod-discourse-sync'); const { contentEl } = this; contentEl.empty(); contentEl.addClass('discourse-sync-modal'); const isUpdate = this.plugin.activeFile.postId !== undefined; - contentEl.createEl("h1", { text: isUpdate ? '更新帖子' : '发布到 Discourse' }); + contentEl.createEl("h1", { text: isUpdate ? t('UPDATE_POST') : t('PUBLISH_TO_DISCOURSE') }); - // 创建表单区域容器 + // Create form area container const formArea = contentEl.createEl('div', { cls: 'form-area' }); - // 创建选择器容器 + // Create selector container const selectContainer = formArea.createEl('div', { cls: 'select-container' }); if (!isUpdate) { - // 只在新建帖子时显示分类选择 - selectContainer.createEl('label', { text: '分类' }); + // Only show category selector when creating a new post + selectContainer.createEl('label', { text: t('CATEGORY') }); const selectEl = selectContainer.createEl('select'); this.categories.forEach(category => { const option = selectEl.createEl('option', { text: category.name }); @@ -465,18 +476,18 @@ export class SelectCategoryModal extends Modal { }); } - // 创建标签选择容器 + // Create tag selector container const tagContainer = formArea.createEl('div', { cls: 'tag-container' }); - tagContainer.createEl('label', { text: '标签' }); + tagContainer.createEl('label', { text: t('TAGS') }); - // 创建标签选择区域 + // Create tag selector area const tagSelectArea = tagContainer.createEl('div', { cls: 'tag-select-area' }); - // 已选标签显示区域 + // Selected tags display area const selectedTagsContainer = tagSelectArea.createEl('div', { cls: 'selected-tags' }); const selectedTags = new Set(); - // 更新已选标签显示 + // Update selected tags display const updateSelectedTags = () => { selectedTagsContainer.empty(); selectedTags.forEach(tag => { @@ -495,19 +506,19 @@ export class SelectCategoryModal extends Modal { }); }; - // 创建标签输入容器 + // Create tag input container const tagInputContainer = tagSelectArea.createEl('div', { cls: 'tag-input-container' }); - // 创建标签输入和建议 + // Create tag input and suggestions const tagInput = tagInputContainer.createEl('input', { type: 'text', - placeholder: this.canCreateTags ? '输入标签名称(回车添加)' : '输入标签名称(回车添加)' + placeholder: this.canCreateTags ? t('ENTER_TAG_WITH_CREATE') : t('ENTER_TAG') }); - // 创建标签建议容器 + // Create tag suggestions container const tagSuggestions = tagInputContainer.createEl('div', { cls: 'tag-suggestions' }); - // 处理输入事件,显示匹配的标签 + // Handle input event, show matching tags tagInput.oninput = () => { const value = tagInput.value.toLowerCase(); tagSuggestions.empty(); @@ -521,14 +532,14 @@ export class SelectCategoryModal extends Modal { .slice(0, 10); if (matches.length > 0) { - // 获取输入框的位置和宽度 + // Get input box position and width const inputRect = tagInput.getBoundingClientRect(); const modalRect = this.modalEl.getBoundingClientRect(); - // 确保建议列表不会超出模态框宽度 - const maxWidth = modalRect.right - inputRect.left - 24; // 24px 是右侧padding + // Ensure suggestions list doesn't exceed modal width + const maxWidth = modalRect.right - inputRect.left - 24; // 24px is right padding - // 设置建议列表的位置和宽度 + // Set suggestions list position and width tagSuggestions.style.top = `${inputRect.bottom + 4}px`; tagSuggestions.style.left = `${inputRect.left}px`; tagSuggestions.style.width = `${Math.min(inputRect.width, maxWidth)}px`; @@ -549,7 +560,7 @@ export class SelectCategoryModal extends Modal { } }; - // 处理回车事件 + // Handle enter event tagInput.onkeydown = (e) => { if (e.key === 'Enter' && tagInput.value) { e.preventDefault(); @@ -563,10 +574,10 @@ export class SelectCategoryModal extends Modal { selectedTags.add(value); updateSelectedTags(); } else { - // 显示权限提示 + // Show permission notice const notice = contentEl.createEl('div', { cls: 'tag-notice', - text: '权限不足,只能使用已有的标签' + text: t('PERMISSION_ERROR') }); setTimeout(() => { notice.remove(); @@ -578,15 +589,15 @@ export class SelectCategoryModal extends Modal { } }; - // 处理失焦事件,隐藏建议 + // Handle blur event, hide suggestions tagInput.onblur = () => { - // 延迟隐藏,以便可以点击建议 + // Delay hide, so suggestions can be clicked setTimeout(() => { tagSuggestions.empty(); }, 200); }; - // 处理窗口滚动时更新建议列表位置 + // Handle window scroll, update suggestions list position const updateSuggestionsPosition = () => { if (tagSuggestions.childNodes.length > 0) { const inputRect = tagInput.getBoundingClientRect(); @@ -596,22 +607,22 @@ export class SelectCategoryModal extends Modal { } }; - // 监听滚动事件 + // Listen for scroll event this.modalEl.addEventListener('scroll', updateSuggestionsPosition); - // 在模态框关闭时移除事件监听 + // Remove event listeners when modal closes this.modalEl.onclose = () => { this.modalEl.removeEventListener('scroll', updateSuggestionsPosition); }; - // 创建按钮区域 + // Create button area const buttonArea = contentEl.createEl('div', { cls: 'button-area' }); const submitButton = buttonArea.createEl('button', { - text: isUpdate ? '更新' : '发布', + text: isUpdate ? t('UPDATE') : t('PUBLISH'), cls: 'submit-button' }); - // 创建提示信息容器 + // Create notice container const noticeContainer = buttonArea.createEl('div'); submitButton.onclick = async () => { @@ -625,25 +636,25 @@ export class SelectCategoryModal extends Modal { await this.plugin.saveSettings(); } - // 保存选中的标签 + // Save selected tags this.plugin.settings.selectedTags = Array.from(selectedTags); await this.plugin.saveSettings(); - // 禁用提交按钮,显示加载状态 + // Disable submit button, show loading state submitButton.disabled = true; - submitButton.textContent = isUpdate ? '更新中...' : '发布中...'; + submitButton.textContent = isUpdate ? t('UPDATING') : t('PUBLISHING'); try { const reply = await this.plugin.postTopic(); - // 显示提示信息 + // Show notice noticeContainer.empty(); if (reply.message === 'Success') { noticeContainer.createEl('div', { cls: 'notice success', - text: isUpdate ? '✓ 更新成功!' : '✓ 发布成功!' + text: isUpdate ? t('UPDATE_SUCCESS') : t('PUBLISH_SUCCESS') }); - // 成功后延迟关闭 + // Close after success setTimeout(() => { this.close(); }, 1500); @@ -651,24 +662,24 @@ export class SelectCategoryModal extends Modal { const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' }); errorContainer.createEl('div', { cls: 'error-title', - text: isUpdate ? '更新失败' : '发布失败' + text: isUpdate ? t('UPDATE_ERROR') : t('PUBLISH_ERROR') }); - // 显示 Discourse 返回的具体错误信息 + // Show Discourse-specific error information errorContainer.createEl('div', { cls: 'error-message', - text: reply.error || (isUpdate ? '更新失败,请重试' : '发布失败,请重试') + text: reply.error || (isUpdate ? t('UPDATE_FAILED') + ', ' + t('TRY_AGAIN') : t('PUBLISH_FAILED') + ', ' + t('TRY_AGAIN')) }); - // 添加重试按钮 + // Add retry button const retryButton = errorContainer.createEl('button', { cls: 'retry-button', - text: '重试' + text: t('RETRY') }); retryButton.onclick = () => { noticeContainer.empty(); submitButton.disabled = false; - submitButton.textContent = isUpdate ? '更新' : '发布'; + submitButton.textContent = isUpdate ? t('UPDATE') : t('PUBLISH'); }; } } catch (error) { @@ -676,29 +687,29 @@ export class SelectCategoryModal extends Modal { const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' }); errorContainer.createEl('div', { cls: 'error-title', - text: isUpdate ? '更新出错' : '发布出错' + text: isUpdate ? t('UPDATE_ERROR') : t('PUBLISH_ERROR') }); errorContainer.createEl('div', { cls: 'error-message', - text: error.message || '未知错误' + text: error.message || t('UNKNOWN_ERROR') }); - // 添加重试按钮 + // Add retry button const retryButton = errorContainer.createEl('button', { cls: 'retry-button', - text: '重试' + text: t('RETRY') }); retryButton.onclick = () => { noticeContainer.empty(); submitButton.disabled = false; - submitButton.textContent = isUpdate ? '更新' : '发布'; + submitButton.textContent = isUpdate ? t('UPDATE') : t('PUBLISH'); }; } - // 如果发生错误,重置按钮状态 + // If error occurs, reset button state if (submitButton.disabled) { submitButton.disabled = false; - submitButton.textContent = isUpdate ? '更新' : '发布'; + submitButton.textContent = isUpdate ? t('UPDATE') : t('PUBLISH'); } }; } diff --git a/styles.css b/styles.css index 1ffa67d..e875641 100644 --- a/styles.css +++ b/styles.css @@ -7,7 +7,7 @@ If your plugin does not need CSS, delete this file. */ -/* 基础模态框样式覆盖 */ +/* Basic Modal Style Override */ .modal.mod-discourse-sync { max-width: 500px; max-height: 90vh; @@ -18,7 +18,7 @@ If your plugin does not need CSS, delete this file. flex-direction: column; } -/* 内容区域样式 */ +/* Content Area Style */ .discourse-sync-modal { padding: 24px; width: 100%; @@ -28,14 +28,14 @@ If your plugin does not need CSS, delete this file. overflow-y: auto; } -/* 表单区域 */ +/* Form Area */ .discourse-sync-modal .form-area { flex-grow: 1; min-height: 0; margin-bottom: 16px; } -/* 按钮区域固定在底部 */ +/* Button Area Fixed at Bottom */ .discourse-sync-modal .button-area { flex-shrink: 0; margin-top: auto; @@ -48,11 +48,11 @@ If your plugin does not need CSS, delete this file. color: var(--text-normal); } -/* 表单容器通用样式 */ +/* Common Form Container Style */ .discourse-sync-modal .select-container, .discourse-sync-modal .tag-container { margin-bottom: 24px; - padding: 0; /* 移除内边距 */ + padding: 0; /* Remove padding */ } .discourse-sync-modal .select-container label, @@ -63,7 +63,7 @@ If your plugin does not need CSS, delete this file. color: var(--text-normal); } -/* 输入框和选择器样式 */ +/* Input and Selector Style */ .discourse-sync-modal select, .discourse-sync-modal .tag-select-area { width: 100%; @@ -108,7 +108,7 @@ If your plugin does not need CSS, delete this file. cursor: not-allowed; } -/* 提示信息样式 */ +/* Notice Style */ .discourse-sync-modal .notice { margin-top: 16px; padding: 16px; @@ -171,7 +171,7 @@ If your plugin does not need CSS, delete this file. color: white; } -/* 标签选择区域样式 */ +/* Tag Select Area Style */ .discourse-sync-modal .tag-select-area { padding: 8px; display: flex; @@ -181,7 +181,7 @@ If your plugin does not need CSS, delete this file. } .discourse-sync-modal .selected-tags { - display: none; /* 默认隐藏 */ + display: none; /* Hidden by default */ flex-wrap: wrap; gap: 6px; margin-bottom: 8px; @@ -189,7 +189,7 @@ If your plugin does not need CSS, delete this file. } .discourse-sync-modal .selected-tags:not(:empty) { - display: flex; /* 当有内容时显示 */ + display: flex; /* Show when has content */ } .discourse-sync-modal .tag { @@ -237,13 +237,13 @@ If your plugin does not need CSS, delete this file. border-bottom: 1px solid var(--background-modifier-border); } -/* 标签输入容器 */ +/* Tag Input Container */ .discourse-sync-modal .tag-input-container { position: relative; width: 100%; } -/* 标签建议下拉框 */ +/* Tag Suggestion Dropdown */ .discourse-sync-modal .tag-suggestions { position: fixed; background-color: var(--background-primary); @@ -254,14 +254,14 @@ If your plugin does not need CSS, delete this file. max-height: 180px; overflow-y: auto; margin-top: 4px; - display: none; /* 默认隐藏 */ + display: none; /* Hidden by default */ } .discourse-sync-modal .tag-suggestions:not(:empty) { - display: block; /* 当有内容时显示 */ + display: block; /* Show when has content */ } -/* 标签建议项 */ +/* Tag Suggestion Item */ .discourse-sync-modal .tag-suggestion { padding: 8px 12px; cursor: pointer; @@ -279,7 +279,7 @@ If your plugin does not need CSS, delete this file. background-color: var(--background-modifier-hover); } -/* 美化滚动条 */ +/* Tag Suggestion Scrollbar Style */ .discourse-sync-modal .tag-suggestions::-webkit-scrollbar { width: 4px; } @@ -297,7 +297,7 @@ If your plugin does not need CSS, delete this file. background-color: var(--background-modifier-border-hover); } -/* 移除 tag-container 的 z-index,避免干扰 */ +/* Remove tag-container z-index to avoid interference */ .discourse-sync-modal .tag-container { position: relative; }