diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d8e68e84..af62f259 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,6 @@ import Modal from './components/common/ResizableDraggableModal'; import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'; -import { Layout, Button, ConfigProvider, theme, message, Spin, Slider, Progress, Switch, Input, InputNumber, Select, Segmented, Tooltip, Alert } from 'antd'; +import { Layout, Button, ConfigProvider, theme, message, Spin, Slider, Switch, Input, InputNumber, Select, Segmented, Tooltip, Alert } from 'antd'; import { UploadOutlined, DownloadOutlined, CloudDownloadOutlined, BugOutlined, GlobalOutlined, InfoCircleOutlined, GithubOutlined, SkinOutlined, CheckOutlined, MinusOutlined, BorderOutlined, CloseOutlined, SettingOutlined, LinkOutlined, BgColorsOutlined, AppstoreOutlined, RobotOutlined, FolderOpenOutlined, HddOutlined, SafetyCertificateOutlined, SwitcherOutlined, CodeOutlined, RightOutlined, TableOutlined, MenuOutlined, MenuFoldOutlined, MenuUnfoldOutlined, PoweroffOutlined, TagOutlined, UserOutlined, UpCircleOutlined, MessageOutlined, FileTextOutlined, SyncOutlined, SendOutlined, AuditOutlined } from '@ant-design/icons'; import { DndContext, PointerSensor, closestCenter, useSensor, useSensors, type DragEndEvent } from '@dnd-kit/core'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; @@ -16,6 +16,12 @@ import NativeDetachedWindowController from './components/NativeDetachedWindowCon import ConnectionModal from './components/ConnectionModal'; import SnippetSettingsModal from './components/SnippetSettingsModal'; import ConnectionPackagePasswordModal from './components/ConnectionPackagePasswordModal'; +import UpdateReleaseNotesModal from './components/UpdateReleaseNotesModal'; +import { + buildReleaseNotesReadKey, + isReleaseNotesRead, + markReleaseNotesRead, +} from './utils/updateReleaseNotesReadState'; import { type DataSyncEntryMode } from './components/dataSyncEntryMode'; import DriverManagerModal from './components/DriverManagerModal'; import LinuxCJKFontBanner from './components/LinuxCJKFontBanner'; @@ -2338,6 +2344,8 @@ function App() { updateCenterBridgeRef, }); const [aboutLastCheckedAt, setAboutLastCheckedAt] = useState(''); + const [releaseNotesModalOpen, setReleaseNotesModalOpen] = useState(false); + const [releaseNotesReadTick, setReleaseNotesReadTick] = useState(0); useEffect(() => { if (!lastUpdateInfo) { return; @@ -2350,6 +2358,47 @@ function App() { lastUpdateInfo?.latestVersion, ]); + const releaseNotesReadKey = useMemo( + () => buildReleaseNotesReadKey(lastUpdateInfo), + [lastUpdateInfo?.channel, lastUpdateInfo?.latestVersion], + ); + // releaseNotesReadTick 强制在 mark 后重算未读态 + const hasUnreadReleaseNotes = useMemo(() => { + void releaseNotesReadTick; + if (!lastUpdateInfo || !releaseNotesReadKey) return false; + // 有正文或至少有 GitHub 链接时,未读才有提示意义 + if (!String(lastUpdateInfo.releaseNotes || '').trim() && !String(lastUpdateInfo.releaseNotesUrl || '').trim()) { + return false; + } + return !isReleaseNotesRead(releaseNotesReadKey); + }, [lastUpdateInfo, releaseNotesReadKey, releaseNotesReadTick]); + + const openReleaseNotesModal = useCallback(() => { + if (!lastUpdateInfo) return; + setReleaseNotesModalOpen(true); + }, [lastUpdateInfo]); + + const closeReleaseNotesModal = useCallback(() => { + setReleaseNotesModalOpen(false); + hideUpdateDownloadProgress(); + }, [hideUpdateDownloadProgress]); + + const handleReleaseNotesModalOpen = useCallback(() => { + if (!releaseNotesReadKey) return; + if (markReleaseNotesRead(releaseNotesReadKey)) { + setReleaseNotesReadTick((value) => value + 1); + } + }, [releaseNotesReadKey]); + + /** 下载与更新日志同窗:点下载即打开弹窗并开始下载 */ + const handleDownloadUpdateWithNotes = useCallback(() => { + if (!lastUpdateInfo) return; + setReleaseNotesModalOpen(true); + void downloadUpdate(lastUpdateInfo, false); + }, [downloadUpdate, lastUpdateInfo]); + + const releaseNotesModalVisible = releaseNotesModalOpen || updateDownloadProgress.open; + const emitWindowDiagnostic = useCallback(async (stage: string, extra: Record = {}) => { if (!macWindowDiagnosticsEnabled) { return; @@ -5175,6 +5224,32 @@ function App() { : (lastUpdateInfo?.packageType === 'portable' ? t('app.about.action.download_portable_update') : t('app.about.action.download_update')); + const renderReleaseNotesActionButton = (key = 'release-notes') => ( + lastUpdateInfo ? ( + + ) : null + ); + const renderAboutUpdateActions = (closeAction?: React.ReactNode) => [ isBackgroundProgressForLatestUpdate && !isLatestUpdateDownloaded ? ( @@ -5182,6 +5257,7 @@ function App() { lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? ( ) : null, + renderReleaseNotesActionButton(), , closeAction ?? null, lastUpdateInfo?.hasUpdate && !isLatestUpdateDownloaded && !isBackgroundProgressForLatestUpdate ? ( - + ) : null, isLatestUpdateDownloaded ? ( + ), + ], ...(installMode === 'msi' || installMode === 'portable' - ? [[t('app.about.version.install_mode'), t(`app.about.install_mode.${installMode}`)]] + ? [[t('app.about.version.install_mode'), t(`app.about.install_mode.${installMode}`)] as [string, React.ReactNode]] : []), ...(hasUpdate && packageType !== 'unknown' - ? [[t('app.about.version.package_type'), t(`app.about.package_type.${packageType}`)]] + ? [[t('app.about.version.package_type'), t(`app.about.package_type.${packageType}`)] as [string, React.ReactNode]] : []), ]; @@ -7504,9 +7606,7 @@ function App() { ); } if (activeSettingsCenterPane.key === 'about-go-navi') { - return ( - renderSettingsCenterAboutPane() - ); + return renderSettingsCenterAboutPane(); } return null; }; @@ -8950,6 +9050,95 @@ function App() { {renderAboutSettingsContent()} + { + const url = lastUpdateInfo?.releaseNotesUrl || aboutInfo?.releaseUrl; + if (!url) return; + try { BrowserOpenURL(url); } catch { window.open(url, '_blank', 'noopener,noreferrer'); } + }} + > + {t('app.about.release_notes.modal.open_github')} + + ) : null, + (updateDownloadProgress.status === 'start' || updateDownloadProgress.status === 'downloading') ? ( + + ) : null, + lastUpdateInfo?.hasUpdate + && !isLatestUpdateDownloaded + && updateDownloadProgress.status !== 'start' + && updateDownloadProgress.status !== 'downloading' ? ( + + ) : null, + isLatestUpdateDownloaded || updateDownloadProgress.status === 'done' ? ( + + ) : null, + isLatestUpdateDownloaded || updateDownloadProgress.status === 'done' ? ( + + ) : null, + (updateDownloadProgress.status !== 'start' && updateDownloadProgress.status !== 'downloading') ? ( + + ) : null, + ].filter(Boolean) as React.ReactNode[]} + /> + {isThemeModalOpen && ( )} - { - markUpdateProgressDismissed(); - hideUpdateDownloadProgress(); - }} - > - {t('app.about.action.hide_to_background')} - - ] : (updateDownloadProgress.status === 'done' ? [ - , - , - - ] : (updateDownloadProgress.status === 'error' ? [ - - ] : null))} - > -
- -
- {updateDownloadProgress.status === 'done' - ? (updateInstallAction === 'restart' - ? t('app.about.download_progress.complete_hint') - : t('app.about.download_progress.installer_complete_hint')) - : `${formatBytes(updateDownloadProgress.downloaded)} / ${formatBytes(updateDownloadProgress.total)}`} -
- {updateDownloadProgress.message ? ( -
- {updateDownloadProgress.message} -
- ) : null} -
-
- {showLinuxResizeHandles && ( <> {/* Linux Mint 下 frameless 仅局部可缩放:补四边四角命中层 */} diff --git a/frontend/src/components/UpdateReleaseNotesModal.test.tsx b/frontend/src/components/UpdateReleaseNotesModal.test.tsx new file mode 100644 index 00000000..050358dd --- /dev/null +++ b/frontend/src/components/UpdateReleaseNotesModal.test.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { create, act, type ReactTestRenderer } from 'react-test-renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('../../wailsjs/runtime', () => ({ + BrowserOpenURL: vi.fn(), +})); + +vi.mock('../i18n', () => ({ + t: (key: string) => key, +})); + +vi.mock('./common/ResizableDraggableModal', () => ({ + default: ({ open, title, children, footer }: any) => ( + open ? ( +
+
{title}
+
{children}
+
{footer}
+
+ ) : null + ), +})); + +import UpdateReleaseNotesModal from './UpdateReleaseNotesModal'; + +const collectText = (value: unknown): string => { + if (value == null || typeof value === 'boolean') return ''; + if (typeof value === 'string' || typeof value === 'number') return String(value); + if (Array.isArray(value)) return value.map(collectText).join(''); + if (typeof value === 'object' && value && 'children' in (value as any)) { + return collectText((value as any).children); + } + if (typeof value === 'object' && value && 'props' in (value as any)) { + return collectText((value as any).props?.children); + } + return ''; +}; + +describe('UpdateReleaseNotesModal', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders markdown release notes body when provided', () => { + let renderer!: ReactTestRenderer; + act(() => { + renderer = create( + undefined} + version="0.9.0" + channel="latest" + releaseNotes={"## Feature\n\n- ship in-app release notes\n"} + releaseNotesUrl="https://github.com/Syngnat/GoNavi/releases/tag/v0.9.0" + />, + ); + }); + const modal = renderer.root.findByProps({ 'data-testid': 'release-notes-modal' }); + const text = collectText(modal); + expect(text).toContain('app.about.release_notes.modal.title'); + expect(text).toContain('0.9.0'); + expect(text).toContain('ship in-app release notes'); + expect(text).toContain('app.about.release_notes.modal.open_github'); + }); + + it('shows empty state when notes body is missing', () => { + let renderer!: ReactTestRenderer; + act(() => { + renderer = create( + undefined} + releaseNotesUrl="https://github.com/Syngnat/GoNavi/releases/latest" + />, + ); + }); + const modal = renderer.root.findByProps({ 'data-testid': 'release-notes-modal' }); + const text = collectText(modal); + expect(text).toContain('app.about.release_notes.modal.empty_with_link'); + }); + + it('renders download progress section alongside notes', () => { + let renderer!: ReactTestRenderer; + act(() => { + renderer = create( + undefined} + version="0.9.1" + releaseNotes={"## Feature\n\n- combined download panel\n"} + downloadProgress={{ + status: 'downloading', + percent: 42, + downloaded: 42, + total: 100, + message: 'downloading…', + }} + formatBytes={(value) => `${value}B`} + />, + ); + }); + const modal = renderer.root.findByProps({ 'data-testid': 'release-notes-modal' }); + const text = collectText(modal); + expect(text).toContain('app.about.release_notes.modal.download_section'); + expect(text).toContain('combined download panel'); + expect(text).toContain('42B / 100B'); + }); +}); diff --git a/frontend/src/components/UpdateReleaseNotesModal.tsx b/frontend/src/components/UpdateReleaseNotesModal.tsx new file mode 100644 index 00000000..a2405526 --- /dev/null +++ b/frontend/src/components/UpdateReleaseNotesModal.tsx @@ -0,0 +1,243 @@ +import React, { useEffect, useMemo } from 'react'; +import { Button, Progress } from 'antd'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { BrowserOpenURL } from '../../wailsjs/runtime'; +import Modal from './common/ResizableDraggableModal'; +import { t } from '../i18n'; + +export type UpdateReleaseNotesDownloadProgress = { + status: 'idle' | 'start' | 'downloading' | 'done' | 'error'; + percent: number; + downloaded: number; + total: number; + message?: string; +}; + +export type UpdateReleaseNotesModalProps = { + open: boolean; + onClose: () => void; + /** 弹窗打开时回调(用于标记已读等) */ + onOpen?: () => void; + darkMode?: boolean; + version?: string; + channel?: string; + releaseName?: string; + releasePublishedAt?: string; + releaseNotes?: string; + releaseNotesUrl?: string; + /** 需高于设置中心等父层弹窗,否则会沉在背后 */ + zIndex?: number; + /** 下载进度(与更新日志同窗展示,可边看边下) */ + downloadProgress?: UpdateReleaseNotesDownloadProgress | null; + formatBytes?: (value: number) => string; + progressHint?: string; + /** 自定义底部操作(下载/安装/后台等) */ + footerActions?: React.ReactNode[]; +}; + +const formatReleaseTime = (value?: string): string => { + const text = String(value || '').trim(); + if (!text) return ''; + const date = new Date(text); + if (Number.isNaN(date.getTime())) return text; + const pad = (n: number) => String(n).padStart(2, '0'); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`; +}; + +/** + * 应用内「更新日志 + 下载进度」统一弹窗: + * 检查更新后可读 changelog;下载时进度与日志同屏,支持边看边下。 + */ +const UpdateReleaseNotesModal: React.FC = ({ + open, + onClose, + onOpen, + darkMode = false, + version, + channel, + releaseName, + releasePublishedAt, + releaseNotes, + releaseNotesUrl, + zIndex, + downloadProgress = null, + formatBytes, + progressHint, + footerActions, +}) => { + const notes = String(releaseNotes || '').trim(); + const url = String(releaseNotesUrl || '').trim(); + const progress = downloadProgress && downloadProgress.status !== 'idle' + ? downloadProgress + : null; + const meta = useMemo(() => { + const parts = [ + String(version || '').trim() || String(releaseName || '').trim(), + String(channel || '').trim(), + formatReleaseTime(releasePublishedAt), + ].filter(Boolean); + return parts.join(' · '); + }, [channel, releaseName, releasePublishedAt, version]); + + useEffect(() => { + if (open) { + onOpen?.(); + } + }, [open, onOpen]); + + const openGitHub = () => { + if (!url) return; + try { + BrowserOpenURL(url); + } catch { + window.open(url, '_blank', 'noopener,noreferrer'); + } + }; + + const progressStatus = progress?.status === 'error' + ? 'exception' + : (progress?.status === 'done' ? 'success' : 'active'); + + const title = progress + ? (version + ? t('app.about.download_progress.title_with_version', { version }) + : t('app.about.download_progress.title')) + : t('app.about.release_notes.modal.title'); + + const defaultFooter = [ + url ? ( + + ) : null, + , + ].filter(Boolean); + + return ( + 0 ? footerActions : defaultFooter} + destroyOnHidden + > + {meta ? ( +
+ {meta} +
+ ) : null} + + {progress ? ( +
+
+ {t('app.about.release_notes.modal.download_section')} +
+ +
+ {progress.status === 'done' + ? (progressHint || t('app.about.download_progress.complete_hint')) + : (formatBytes + ? `${formatBytes(progress.downloaded)} / ${formatBytes(progress.total)}` + : `${Math.round(progress.percent)}%`)} +
+ {progress.message ? ( +
+ {progress.message} +
+ ) : null} +
+ ) : null} + +
+ {notes ? ( + ( + { + event.preventDefault(); + if (href) { + try { + BrowserOpenURL(href); + } catch { + window.open(href, '_blank', 'noopener,noreferrer'); + } + } + }} + > + {children} + + ), + }} + skipHtml + > + {notes} + + ) : ( +
+ {url + ? t('app.about.release_notes.modal.empty_with_link') + : t('app.about.release_notes.modal.empty')} +
+ )} +
+
+ ); +}; + +export default UpdateReleaseNotesModal; diff --git a/frontend/src/hooks/useAppUpdateManager.ts b/frontend/src/hooks/useAppUpdateManager.ts index 75b496f4..dff9be4e 100644 --- a/frontend/src/hooks/useAppUpdateManager.ts +++ b/frontend/src/hooks/useAppUpdateManager.ts @@ -20,6 +20,8 @@ export type UpdateInfo = { releaseName?: string; releasePublishedAt?: string; releaseNotesUrl?: string; + /** Markdown 更新日志正文(来自 latest.json / GitHub release body) */ + releaseNotes?: string; assetName?: string; assetUrl?: string; assetSize?: number; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 372832a1..502ff67e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -136,6 +136,9 @@ if ( releaseNotesUrl: mockUpdateChannel === 'dev' ? 'https://github.com/Syngnat/GoNavi/releases/tag/dev-latest' : 'https://github.com/Syngnat/GoNavi/releases/latest', + releaseNotes: mockUpdateChannel === 'dev' + ? '## 🧪 测试版本 (Dev Build)\n\n## ✨ 新功能\n\n- 浏览器 mock:dev 通道更新日志样例\n' + : '## ✨ 新功能\n\n- 浏览器 mock:latest 通道更新日志样例\n\n## 🐛 问题修复\n\n- 示例修复项\n', }); let mockDataRootInfo: any = { path: 'C:/mock/.gonavi', diff --git a/frontend/src/utils/updateReleaseNotesReadState.test.ts b/frontend/src/utils/updateReleaseNotesReadState.test.ts new file mode 100644 index 00000000..b74288a6 --- /dev/null +++ b/frontend/src/utils/updateReleaseNotesReadState.test.ts @@ -0,0 +1,45 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { + buildReleaseNotesReadKey, + clearReleaseNotesReadState, + isReleaseNotesRead, + loadReadReleaseNotesKeys, + markReleaseNotesRead, +} from './updateReleaseNotesReadState'; + +describe('updateReleaseNotesReadState', () => { + let memory: Record; + + beforeEach(() => { + memory = {}; + vi.stubGlobal('localStorage', { + getItem: (key: string) => (key in memory ? memory[key] : null), + setItem: (key: string, value: string) => { + memory[key] = String(value); + }, + removeItem: (key: string) => { + delete memory[key]; + }, + }); + clearReleaseNotesReadState(); + }); + + afterEach(() => { + clearReleaseNotesReadState(); + vi.unstubAllGlobals(); + }); + + it('builds stable channel:version keys', () => { + expect(buildReleaseNotesReadKey({ channel: 'Latest', latestVersion: '0.9.0' })).toBe('latest:0.9.0'); + expect(buildReleaseNotesReadKey({ channel: 'dev', latestVersion: 'dev-abc1234' })).toBe('dev:dev-abc1234'); + expect(buildReleaseNotesReadKey({ latestVersion: '' })).toBe(''); + }); + + it('marks and loads read keys from localStorage', () => { + expect(isReleaseNotesRead('latest:0.9.0')).toBe(false); + expect(markReleaseNotesRead('latest:0.9.0')).toBe(true); + expect(markReleaseNotesRead('latest:0.9.0')).toBe(false); + expect(isReleaseNotesRead('latest:0.9.0')).toBe(true); + expect(loadReadReleaseNotesKeys().has('latest:0.9.0')).toBe(true); + }); +}); diff --git a/frontend/src/utils/updateReleaseNotesReadState.ts b/frontend/src/utils/updateReleaseNotesReadState.ts new file mode 100644 index 00000000..fca78485 --- /dev/null +++ b/frontend/src/utils/updateReleaseNotesReadState.ts @@ -0,0 +1,72 @@ +/** + * 本地缓存「已读」的更新日志版本,用于关于页/更新提示条上的未读提示。 + * key 形态:channel:version(与安装包名无关,便于同一版本多包形态共用已读状态) + */ + +const STORAGE_KEY = 'gonavi.updateReleaseNotes.readKeys.v1'; +const MAX_KEYS = 40; + +export type ReleaseNotesReadIdentity = { + channel?: string | null; + latestVersion?: string | null; +}; + +export const buildReleaseNotesReadKey = ( + info: ReleaseNotesReadIdentity | null | undefined, +): string => { + const version = String(info?.latestVersion || '').trim(); + if (!version) return ''; + const channel = String(info?.channel || 'latest').trim().toLowerCase() || 'latest'; + return `${channel}:${version}`; +}; + +const readStorage = (): string[] => { + if (typeof localStorage === 'undefined') return []; + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return []; + const parsed = JSON.parse(raw); + if (!Array.isArray(parsed)) return []; + return parsed + .map((item) => String(item || '').trim()) + .filter(Boolean); + } catch { + return []; + } +}; + +const writeStorage = (keys: string[]): void => { + if (typeof localStorage === 'undefined') return; + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(keys.slice(0, MAX_KEYS))); + } catch { + // ignore quota / private mode + } +}; + +export const loadReadReleaseNotesKeys = (): Set => new Set(readStorage()); + +export const isReleaseNotesRead = (key: string): boolean => { + const normalized = String(key || '').trim(); + if (!normalized) return false; + return loadReadReleaseNotesKeys().has(normalized); +}; + +/** 标记已读;返回是否状态发生变化。 */ +export const markReleaseNotesRead = (key: string): boolean => { + const normalized = String(key || '').trim(); + if (!normalized) return false; + const current = readStorage(); + if (current.includes(normalized)) return false; + writeStorage([normalized, ...current.filter((item) => item !== normalized)]); + return true; +}; + +export const clearReleaseNotesReadState = (): void => { + if (typeof localStorage === 'undefined') return; + try { + localStorage.removeItem(STORAGE_KEY); + } catch { + // ignore + } +}; diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index 4d911aa0..775d9dcc 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -2500,7 +2500,7 @@ "app.about.update_status.not_checked": "Nicht geprüft", "app.about.version.current": "Aktuelle Version", "app.about.version.latest": "Neueste Version", - "app.about.version.release_notes": "Updateinhalt", + "app.about.version.release_notes": "Änderungsprotokoll", "app.about.version.release_time": "Veröffentlichungszeit", "app.about.version_update.available": "Update verfügbar", "app.about.version_update.auto_check_disabled_hint": "Wenn deaktiviert, werden Updates nur über die manuelle Aktion „Nach Updates suchen“ geprüft", @@ -9216,5 +9216,12 @@ "nacos_viewer.action.open_group_configs": "Group-Konfigurationen öffnen", "nacos_viewer.label.all": "Alle", "nacos_viewer.pagination.range": "{{from}}-{{to}} von {{total}}", - "nacos_viewer.pagination.empty": "Keine Konfigurationen" + "nacos_viewer.pagination.empty": "Keine Konfigurationen", + "app.about.release_notes.action.view": "Änderungsprotokoll anzeigen", + "app.about.release_notes.modal.title": "Änderungsprotokoll", + "app.about.release_notes.modal.open_github": "Auf GitHub öffnen", + "app.about.release_notes.modal.empty": "Kein Änderungsprotokoll verfügbar.", + "app.about.release_notes.modal.empty_with_link": "Kein Änderungsprotokoll-Text verfügbar. Vollständige Infos auf der GitHub-Release-Seite.", + "app.about.release_notes.unread_badge": "Ungelesenes Änderungsprotokoll", + "app.about.release_notes.modal.download_section": "Download-Fortschritt" } diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index 48e632e9..4a246b7a 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -2500,7 +2500,7 @@ "app.about.update_status.not_checked": "Not checked", "app.about.version.current": "Current Version", "app.about.version.latest": "Latest Version", - "app.about.version.release_notes": "Update Notes", + "app.about.version.release_notes": "Release notes", "app.about.version.release_time": "Release Time", "app.about.version_update.available": "Update available", "app.about.version_update.auto_check_disabled_hint": "When off, updates are only checked via the manual Check for Updates action", @@ -9216,5 +9216,12 @@ "nacos_viewer.action.open_group_configs": "Open group configs", "nacos_viewer.label.all": "All", "nacos_viewer.pagination.range": "{{from}}-{{to}} of {{total}}", - "nacos_viewer.pagination.empty": "No configs" + "nacos_viewer.pagination.empty": "No configs", + "app.about.release_notes.action.view": "View release notes", + "app.about.release_notes.modal.title": "Release notes", + "app.about.release_notes.modal.open_github": "Open on GitHub", + "app.about.release_notes.modal.empty": "No release notes are available.", + "app.about.release_notes.modal.empty_with_link": "No release notes body is available. You can open the GitHub release for full details.", + "app.about.release_notes.unread_badge": "Unread release notes", + "app.about.release_notes.modal.download_section": "Download progress" } diff --git a/shared/i18n/ja-JP.json b/shared/i18n/ja-JP.json index 29b5fe51..87c992a1 100644 --- a/shared/i18n/ja-JP.json +++ b/shared/i18n/ja-JP.json @@ -2500,7 +2500,7 @@ "app.about.update_status.not_checked": "未確認", "app.about.version.current": "現在のバージョン", "app.about.version.latest": "最新バージョン", - "app.about.version.release_notes": "更新内容", + "app.about.version.release_notes": "更新ログ", "app.about.version.release_time": "公開日時", "app.about.version_update.available": "利用可能な更新があります", "app.about.version_update.auto_check_disabled_hint": "オフの場合、手動の「更新を確認」でのみ新バージョンを取得します", @@ -9216,5 +9216,12 @@ "nacos_viewer.action.open_group_configs": "この Group の設定を開く", "nacos_viewer.label.all": "すべて", "nacos_viewer.pagination.range": "{{from}}-{{to}} / 全 {{total}} 件", - "nacos_viewer.pagination.empty": "設定がありません" + "nacos_viewer.pagination.empty": "設定がありません", + "app.about.release_notes.action.view": "更新ログを表示", + "app.about.release_notes.modal.title": "更新ログ", + "app.about.release_notes.modal.open_github": "GitHub で開く", + "app.about.release_notes.modal.empty": "更新ログ本文はありません。", + "app.about.release_notes.modal.empty_with_link": "更新ログ本文はありません。GitHub のリリースで詳細を確認できます。", + "app.about.release_notes.unread_badge": "未読の更新ログ", + "app.about.release_notes.modal.download_section": "ダウンロード状況" } diff --git a/shared/i18n/ru-RU.json b/shared/i18n/ru-RU.json index c1347e55..85754eef 100644 --- a/shared/i18n/ru-RU.json +++ b/shared/i18n/ru-RU.json @@ -2500,7 +2500,7 @@ "app.about.update_status.not_checked": "Не проверялось", "app.about.version.current": "Текущая версия", "app.about.version.latest": "Последняя версия", - "app.about.version.release_notes": "Содержание обновления", + "app.about.version.release_notes": "Журнал изменений", "app.about.version.release_time": "Время выпуска", "app.about.version_update.available": "Доступно обновление", "app.about.version_update.auto_check_disabled_hint": "При отключении обновления проверяются только вручную через «Проверить обновления»", @@ -9216,5 +9216,12 @@ "nacos_viewer.action.open_group_configs": "Открыть конфигурации группы", "nacos_viewer.label.all": "Все", "nacos_viewer.pagination.range": "{{from}}-{{to}} из {{total}}", - "nacos_viewer.pagination.empty": "Нет конфигураций" + "nacos_viewer.pagination.empty": "Нет конфигураций", + "app.about.release_notes.action.view": "Посмотреть журнал изменений", + "app.about.release_notes.modal.title": "Журнал изменений", + "app.about.release_notes.modal.open_github": "Открыть на GitHub", + "app.about.release_notes.modal.empty": "Текст журнала изменений недоступен.", + "app.about.release_notes.modal.empty_with_link": "Текст журнала изменений недоступен. Полные сведения — на странице GitHub Release.", + "app.about.release_notes.unread_badge": "Непрочитанный журнал изменений", + "app.about.release_notes.modal.download_section": "Прогресс загрузки" } diff --git a/shared/i18n/zh-CN.json b/shared/i18n/zh-CN.json index fb294d63..7b9b0f34 100644 --- a/shared/i18n/zh-CN.json +++ b/shared/i18n/zh-CN.json @@ -2500,7 +2500,7 @@ "app.about.update_status.not_checked": "未检查", "app.about.version.current": "当前版本", "app.about.version.latest": "最新版本", - "app.about.version.release_notes": "更新内容", + "app.about.version.release_notes": "更新日志", "app.about.version.release_time": "发布时间", "app.about.version_update.available": "有可用更新", "app.about.version_update.auto_check_disabled_hint": "关闭后仅可通过手动「检查更新」获取新版本", @@ -9216,5 +9216,12 @@ "nacos_viewer.action.open_group_configs": "打开该 Group 配置", "nacos_viewer.label.all": "全部", "nacos_viewer.pagination.range": "第 {{from}}-{{to}} 条 / 共 {{total}} 条", - "nacos_viewer.pagination.empty": "暂无配置" + "nacos_viewer.pagination.empty": "暂无配置", + "app.about.release_notes.action.view": "查看更新日志", + "app.about.release_notes.modal.title": "更新日志", + "app.about.release_notes.modal.open_github": "在 GitHub 打开", + "app.about.release_notes.modal.empty": "暂无更新日志正文。", + "app.about.release_notes.modal.empty_with_link": "暂无更新日志正文,可前往 GitHub Release 查看完整说明。", + "app.about.release_notes.unread_badge": "未读更新日志", + "app.about.release_notes.modal.download_section": "下载进度" } diff --git a/shared/i18n/zh-TW.json b/shared/i18n/zh-TW.json index f8d64004..567c7fbf 100644 --- a/shared/i18n/zh-TW.json +++ b/shared/i18n/zh-TW.json @@ -2500,7 +2500,7 @@ "app.about.update_status.not_checked": "尚未檢查", "app.about.version.current": "目前版本", "app.about.version.latest": "最新版本", - "app.about.version.release_notes": "更新內容", + "app.about.version.release_notes": "更新日誌", "app.about.version.release_time": "發布時間", "app.about.version_update.available": "有可用更新", "app.about.version_update.auto_check_disabled_hint": "關閉後僅可透過手動「檢查更新」取得新版本", @@ -9216,5 +9216,12 @@ "nacos_viewer.action.open_group_configs": "打開該 Group 設定", "nacos_viewer.label.all": "全部", "nacos_viewer.pagination.range": "第 {{from}}-{{to}} 條 / 共 {{total}} 條", - "nacos_viewer.pagination.empty": "暫無設定" + "nacos_viewer.pagination.empty": "暫無設定", + "app.about.release_notes.action.view": "查看更新日誌", + "app.about.release_notes.modal.title": "更新日誌", + "app.about.release_notes.modal.open_github": "在 GitHub 開啟", + "app.about.release_notes.modal.empty": "暫無更新日誌正文。", + "app.about.release_notes.modal.empty_with_link": "暫無更新日誌正文,可前往 GitHub Release 查看完整說明。", + "app.about.release_notes.unread_badge": "未讀更新日誌", + "app.about.release_notes.modal.download_section": "下載進度" }