完善浏览器配置库与未保存提示

- 新增 IndexedDB 配置保存、加载、删除及批量导入导出

- 配置列表支持选中、双击加载和悬浮操作提示

- 新增未保存提示设置,默认关闭并持久化到浏览器

- 优化配置管理界面并同步九种语言文案

- 更新开发文档与生产构建产物
This commit is contained in:
tianmo committed 2026-10-23 11:48:21 +08:00
1 parent 8bd9154414
commit 46083c8d2a
30 files changed
+2244 -1293

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
import { readFile, writeFile } from 'node:fs/promises'
const local = {
'zh-CN': {
listTitle: '配置列表', empty: '浏览器中还没有保存的配置', saveTitle: '保存配置到浏览器缓存',
name: '配置名称', namePlaceholder: '例如:烟花效果', load: '加载', loadHint: '双击以加载', download: '下载', delete: '删除',
downloadCurrent: '下载当前配置', downloadAll: '下载所有配置', loadAll: '加载所有配置',
loadAllConfirm: '将导入 {count} 个配置文件,同名配置会被覆盖。是否继续?',
loadAllDone: '已从文件加载 {count} 个配置', loadAllPartial: '已导入 {imported} 个配置,{failed} 个文件失败',
overwriteSave: '“{name}”已存在,是否覆盖?',
loadOverwrite: '加载“{name}”会覆盖当前场景,未保存的修改将丢失。是否继续?',
deleteConfirm: '确定删除“{name}”吗?', nameRequired: '请输入配置名称',
nameInvalid: '配置名称不能包含 \\ / : * ? " < > |', storageUnavailable: '浏览器本地存储不可用',
saved: '已保存到浏览器:{name}', loaded: '已从浏览器加载:{name}', deleted: '已删除缓存配置:{name}',
downloadEmpty: '没有可下载的配置', downloaded: '已下载:{name}',
},
'zh-TW': {
listTitle: '設定列表', empty: '瀏覽器中尚無已儲存的設定', saveTitle: '儲存設定到瀏覽器快取',
name: '設定名稱', namePlaceholder: '例如:煙花效果', load: '載入', loadHint: '按兩下以載入', download: '下載', delete: '刪除',
downloadCurrent: '下載目前設定', downloadAll: '下載所有設定', loadAll: '載入所有設定',
loadAllConfirm: '將匯入 {count} 個設定檔,同名設定會被覆寫。是否繼續?',
loadAllDone: '已從檔案載入 {count} 個設定', loadAllPartial: '已匯入 {imported} 個設定,{failed} 個檔案失敗',
overwriteSave: '「{name}」已存在,是否覆寫?',
loadOverwrite: '載入「{name}」會覆寫目前場景,未儲存的修改將遺失。是否繼續?',
deleteConfirm: '確定刪除「{name}」嗎?', nameRequired: '請輸入設定名稱',
nameInvalid: '設定名稱不能包含 \\ / : * ? " < > |', storageUnavailable: '瀏覽器本機儲存空間無法使用',
saved: '已儲存到瀏覽器:{name}', loaded: '已從瀏覽器載入:{name}', deleted: '已刪除瀏覽器設定:{name}',
downloadEmpty: '沒有可下載的設定', downloaded: '已下載:{name}',
},
en: {
listTitle: 'Configuration List', empty: 'No configurations saved in this browser', saveTitle: 'Save configuration to browser cache',
name: 'Configuration name', namePlaceholder: 'For example: Firework effect', load: 'Load', loadHint: 'Double-click to load', download: 'Download', delete: 'Delete',
downloadCurrent: 'Download Current Configuration', downloadAll: 'Download All Configurations', loadAll: 'Load All Configurations',
loadAllConfirm: 'Import {count} configuration files? Existing configurations with the same names will be overwritten.',
loadAllDone: 'Loaded {count} configurations from files', loadAllPartial: 'Imported {imported} configurations; {failed} files failed',
overwriteSave: '“{name}” already exists. Overwrite it?',
loadOverwrite: 'Loading “{name}” will replace the current scene and discard unsaved changes. Continue?',
deleteConfirm: 'Delete “{name}”?', nameRequired: 'Enter a configuration name',
nameInvalid: 'The name cannot contain \\ / : * ? " < > |', storageUnavailable: 'Browser local storage is unavailable',
saved: 'Saved to browser: {name}', loaded: 'Loaded from browser: {name}', deleted: 'Deleted browser configuration: {name}',
downloadEmpty: 'No configurations to download', downloaded: 'Downloaded: {name}',
},
ja: {
listTitle: '設定一覧', empty: 'このブラウザに保存された設定はありません', saveTitle: '設定をブラウザキャッシュに保存',
name: '設定名', namePlaceholder: '例:花火エフェクト', load: '読み込む', loadHint: 'ダブルクリックで読み込む', download: 'ダウンロード', delete: '削除',
downloadCurrent: '現在の設定をダウンロード', downloadAll: 'すべての設定をダウンロード', loadAll: 'すべての設定を読み込む',
loadAllConfirm: '{count} 個の設定ファイルを読み込みます。同名の設定は上書きされます。続行しますか?',
loadAllDone: 'ファイルから {count} 個の設定を読み込みました', loadAllPartial: '{imported} 個の設定を読み込み、{failed} 個のファイルが失敗しました',
overwriteSave: '「{name}」は既に存在します。上書きしますか?',
loadOverwrite: '「{name}」を読み込むと現在のシーンが置き換えられ、未保存の変更は失われます。続行しますか?',
deleteConfirm: '「{name}」を削除しますか?', nameRequired: '設定名を入力してください',
nameInvalid: '設定名に \\ / : * ? " < > | は使用できません', storageUnavailable: 'ブラウザのローカルストレージを利用できません',
saved: 'ブラウザに保存しました:{name}', loaded: 'ブラウザから読み込みました:{name}', deleted: 'ブラウザの設定を削除しました:{name}',
downloadEmpty: 'ダウンロードできる設定がありません', downloaded: 'ダウンロードしました:{name}',
},
ko: {
listTitle: '설정 목록', empty: '이 브라우저에 저장된 설정이 없습니다', saveTitle: '브라우저 캐시에 설정 저장',
name: '설정 이름', namePlaceholder: '예: 폭죽 효과', load: '불러오기', loadHint: '더블클릭하여 불러오기', download: '다운로드', delete: '삭제',
downloadCurrent: '현재 설정 다운로드', downloadAll: '모든 설정 다운로드', loadAll: '모든 설정 불러오기',
loadAllConfirm: '설정 파일 {count}개를 가져옵니다. 같은 이름의 설정은 덮어씁니다. 계속할까요?',
loadAllDone: '파일에서 설정 {count}개를 불러왔습니다', loadAllPartial: '설정 {imported}개를 가져왔고 {failed}개 파일이 실패했습니다',
overwriteSave: "'{name}'이(가) 이미 있습니다. 덮어쓸까요?",
loadOverwrite: "'{name}'을(를) 불러오면 현재 장면이 교체되고 저장하지 않은 변경 사항이 사라집니다. 계속할까요?",
deleteConfirm: "'{name}'을(를) 삭제할까요?", nameRequired: '설정 이름을 입력하세요',
nameInvalid: '설정 이름에는 \\ / : * ? " < > | 문자를 사용할 수 없습니다', storageUnavailable: '브라우저 로컬 저장소를 사용할 수 없습니다',
saved: '브라우저에 저장했습니다: {name}', loaded: '브라우저에서 불러왔습니다: {name}', deleted: '브라우저 설정을 삭제했습니다: {name}',
downloadEmpty: '다운로드할 설정이 없습니다', downloaded: '다운로드했습니다: {name}',
},
fr: {
listTitle: 'Liste des configurations', empty: 'Aucune configuration enregistrée dans ce navigateur', saveTitle: 'Enregistrer la configuration dans le cache du navigateur',
name: 'Nom de la configuration', namePlaceholder: 'Par exemple : Effet de feux d’artifice', load: 'Charger', loadHint: 'Double-cliquez pour charger', download: 'Télécharger', delete: 'Supprimer',
downloadCurrent: 'Télécharger la configuration actuelle', downloadAll: 'Télécharger toutes les configurations', loadAll: 'Charger toutes les configurations',
loadAllConfirm: 'Importer {count} fichiers de configuration ? Les configurations portant le même nom seront écrasées.',
loadAllDone: '{count} configurations chargées depuis les fichiers', loadAllPartial: '{imported} configurations importées ; {failed} fichiers en échec',
overwriteSave: '« {name} » existe déjà. L’écraser ?',
loadOverwrite: 'Le chargement de « {name} » remplacera la scène actuelle et perdra les modifications non enregistrées. Continuer ?',
deleteConfirm: 'Supprimer « {name} » ?', nameRequired: 'Saisissez un nom de configuration',
nameInvalid: 'Le nom ne peut pas contenir \\ / : * ? " < > |', storageUnavailable: 'Le stockage local du navigateur est indisponible',
saved: 'Enregistré dans le navigateur : {name}', loaded: 'Chargé depuis le navigateur : {name}', deleted: 'Configuration supprimée du navigateur : {name}',
downloadEmpty: 'Aucune configuration à télécharger', downloaded: 'Téléchargé : {name}',
},
de: {
listTitle: 'Konfigurationsliste', empty: 'Keine Konfigurationen in diesem Browser gespeichert', saveTitle: 'Konfiguration im Browser-Cache speichern',
name: 'Konfigurationsname', namePlaceholder: 'Zum Beispiel: Feuerwerkseffekt', load: 'Laden', loadHint: 'Doppelklicken zum Laden', download: 'Herunterladen', delete: 'Löschen',
downloadCurrent: 'Aktuelle Konfiguration herunterladen', downloadAll: 'Alle Konfigurationen herunterladen', loadAll: 'Alle Konfigurationen laden',
loadAllConfirm: '{count} Konfigurationsdateien importieren? Gleichnamige Konfigurationen werden überschrieben.',
loadAllDone: '{count} Konfigurationen aus Dateien geladen', loadAllPartial: '{imported} Konfigurationen importiert; {failed} Dateien fehlgeschlagen',
overwriteSave: '„{name}“ existiert bereits. Überschreiben?',
loadOverwrite: 'Beim Laden von „{name}“ wird die aktuelle Szene ersetzt und ungespeicherte Änderungen gehen verloren. Fortfahren?',
deleteConfirm: '„{name}“ löschen?', nameRequired: 'Geben Sie einen Konfigurationsnamen ein',
nameInvalid: 'Der Name darf \\ / : * ? " < > | nicht enthalten', storageUnavailable: 'Der lokale Browserspeicher ist nicht verfügbar',
saved: 'Im Browser gespeichert: {name}', loaded: 'Aus dem Browser geladen: {name}', deleted: 'Browserkonfiguration gelöscht: {name}',
downloadEmpty: 'Keine Konfigurationen zum Herunterladen', downloaded: 'Heruntergeladen: {name}',
},
'pt-BR': {
listTitle: 'Lista de configurações', empty: 'Nenhuma configuração salva neste navegador', saveTitle: 'Salvar configuração no cache do navegador',
name: 'Nome da configuração', namePlaceholder: 'Por exemplo: Efeito de fogos', load: 'Carregar', loadHint: 'Clique duplo para carregar', download: 'Baixar', delete: 'Excluir',
downloadCurrent: 'Baixar configuração atual', downloadAll: 'Baixar todas as configurações', loadAll: 'Carregar todas as configurações',
loadAllConfirm: 'Importar {count} arquivos de configuração? Configurações com o mesmo nome serão substituídas.',
loadAllDone: '{count} configurações carregadas dos arquivos', loadAllPartial: '{imported} configurações importadas; {failed} arquivos falharam',
overwriteSave: '“{name}” já existe. Substituir?',
loadOverwrite: 'Carregar “{name}” substituirá a cena atual e descartará alterações não salvas. Continuar?',
deleteConfirm: 'Excluir “{name}”?', nameRequired: 'Digite um nome de configuração',
nameInvalid: 'O nome não pode conter \\ / : * ? " < > |', storageUnavailable: 'O armazenamento local do navegador não está disponível',
saved: 'Salvo no navegador: {name}', loaded: 'Carregado do navegador: {name}', deleted: 'Configuração excluída do navegador: {name}',
downloadEmpty: 'Nenhuma configuração para baixar', downloaded: 'Baixado: {name}',
},
es: {
listTitle: 'Lista de configuraciones', empty: 'No hay configuraciones guardadas en este navegador', saveTitle: 'Guardar configuración en la caché del navegador',
name: 'Nombre de la configuración', namePlaceholder: 'Por ejemplo: Efecto de fuegos artificiales', load: 'Cargar', loadHint: 'Doble clic para cargar', download: 'Descargar', delete: 'Eliminar',
downloadCurrent: 'Descargar configuración actual', downloadAll: 'Descargar todas las configuraciones', loadAll: 'Cargar todas las configuraciones',
loadAllConfirm: '¿Importar {count} archivos de configuración? Las configuraciones con el mismo nombre se sobrescribirán.',
loadAllDone: '{count} configuraciones cargadas desde archivos', loadAllPartial: '{imported} configuraciones importadas; {failed} archivos fallaron',
overwriteSave: '«{name}» ya existe. ¿Sobrescribir?',
loadOverwrite: 'Cargar «{name}» sustituirá la escena actual y descartará los cambios no guardados. ¿Continuar?',
deleteConfirm: '¿Eliminar «{name}»?', nameRequired: 'Introduce un nombre de configuración',
nameInvalid: 'El nombre no puede contener \\ / : * ? " < > |', storageUnavailable: 'El almacenamiento local del navegador no está disponible',
saved: 'Guardado en el navegador: {name}', loaded: 'Cargado desde el navegador: {name}', deleted: 'Configuración eliminada del navegador: {name}',
downloadEmpty: 'No hay configuraciones para descargar', downloaded: 'Descargado: {name}',
},
}
for (const [locale, values] of Object.entries(local)) {
const path = `src/i18n/locales/${locale}.json`
const messages = JSON.parse(await readFile(path, 'utf8'))
messages.config ||= {}
messages.config.local = values
messages.config.saveTitle = values.saveTitle
await writeFile(path, `${JSON.stringify(messages, null, 2)}\n`)
}
console.log('Applied project library translations.')