diff --git a/src/i18n/locales/de-DE.ts b/src/i18n/locales/de-DE.ts
index 41f9e943..916ac5aa 100644
--- a/src/i18n/locales/de-DE.ts
+++ b/src/i18n/locales/de-DE.ts
@@ -1018,6 +1018,14 @@ export const deDE: LanguagePack = {
'Diese Registry-Exporte stimmen überein. Keine Schlüssel- oder Wertunterschiede anzuzeigen.',
'ui.registryNeedsBothExports': 'Fügen Sie zuerst beide .reg-Exporte (links und rechts) ein.',
'ui.registryExportPlaceholder': 'Hier einen .reg-Export einfügen',
+ 'ui.versionEmptyCompareHint':
+ 'Wählen Sie linke und rechte Programmdateien mit Durchsuchen (oder fügen Sie Pfade ein), dann auf Vergleichen klicken.',
+ 'ui.versionEmptyReadyHint':
+ 'Drücken Sie Enter oder klicken Sie auf Vergleichen, um diese Versionsressourcen zu vergleichen.',
+ 'ui.versionIdenticalHint':
+ 'Diese Programmdateien stimmen überein. Keine Versionsressourcen-Unterschiede anzuzeigen.',
+ 'ui.versionCompareWindowsOnly':
+ 'Das Lesen nativer Versionsressourcen ist nur unter Windows verfügbar. Sie können hier trotzdem Pfade wählen; Vergleich erklärt, falls diese Plattform sie nicht lesen kann.',
'ui.textPatchSourceTextPlaceholder': 'Originaltextdatei für „Patch anwenden“ einfügen',
'ui.applyPatchHint': 'Zeigt den gepatchten Text im Speicher an (schreibt keine Datei).',
'ui.applyToFileHint': 'Schreibt das Ergebnis in die Zieldatei (oder Quelle, wenn Ziel leer).',
diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts
index 493aaa4c..0639c81b 100644
--- a/src/i18n/locales/en-US.ts
+++ b/src/i18n/locales/en-US.ts
@@ -979,6 +979,13 @@ export const enUS: LanguagePack = {
'These registry exports match. No key or value differences to show.',
'ui.registryNeedsBothExports': 'Paste both left and right .reg exports first.',
'ui.registryExportPlaceholder': 'Paste a .reg export here',
+ 'ui.versionEmptyCompareHint':
+ 'Choose left and right program files with Browse (or paste paths), then click Compare.',
+ 'ui.versionEmptyReadyHint': 'Press Enter or click Compare to compare these version resources.',
+ 'ui.versionIdenticalHint':
+ 'These program files match. No version-resource differences to show.',
+ 'ui.versionCompareWindowsOnly':
+ 'Native version resource reading is only available on Windows. You can still choose paths here; compare will explain if this platform cannot read them.',
'ui.textPatchSourceTextPlaceholder': 'Paste the original file text for Apply Patch',
'ui.applyPatchHint': 'Preview the patched text in memory (does not write a file).',
'ui.applyToFileHint':
diff --git a/src/i18n/locales/es-ES.ts b/src/i18n/locales/es-ES.ts
index 85e8671d..a17796af 100644
--- a/src/i18n/locales/es-ES.ts
+++ b/src/i18n/locales/es-ES.ts
@@ -1002,6 +1002,14 @@ export const esES: LanguagePack = {
'Estas exportaciones del registro coinciden. No hay diferencias de claves o valores que mostrar.',
'ui.registryNeedsBothExports': 'Pegue primero ambas exportaciones .reg (izquierda y derecha).',
'ui.registryExportPlaceholder': 'Pegue aquí una exportación .reg',
+ 'ui.versionEmptyCompareHint':
+ 'Elija los archivos de programa izquierdo y derecho con Examinar (o pegue rutas) y luego haga clic en Comparar.',
+ 'ui.versionEmptyReadyHint':
+ 'Pulse Intro o haga clic en Comparar para comparar estos recursos de versión.',
+ 'ui.versionIdenticalHint':
+ 'Estos archivos de programa coinciden. No hay diferencias de recursos de versión que mostrar.',
+ 'ui.versionCompareWindowsOnly':
+ 'La lectura nativa de recursos de versión solo está disponible en Windows. Aún puede elegir rutas aquí; la comparación explicará si esta plataforma no puede leerlas.',
'ui.textPatchSourceTextPlaceholder': 'Pegue el texto del archivo original para Aplicar parche',
'ui.applyPatchHint': 'Previsualiza el texto parcheado en memoria (no escribe un archivo).',
'ui.applyToFileHint':
diff --git a/src/i18n/locales/fr-FR.ts b/src/i18n/locales/fr-FR.ts
index 609fd84e..be10cd77 100644
--- a/src/i18n/locales/fr-FR.ts
+++ b/src/i18n/locales/fr-FR.ts
@@ -1012,6 +1012,14 @@ export const frFR: LanguagePack = {
'Ces exports du Registre correspondent. Aucune différence de clé ou de valeur à afficher.',
'ui.registryNeedsBothExports': "Collez d'abord les deux exports .reg (gauche et droit).",
'ui.registryExportPlaceholder': 'Collez un export .reg ici',
+ 'ui.versionEmptyCompareHint':
+ 'Choisissez les fichiers programme gauche et droit avec Parcourir (ou collez des chemins), puis cliquez sur Comparer.',
+ 'ui.versionEmptyReadyHint':
+ 'Appuyez sur Entrée ou cliquez sur Comparer pour comparer ces ressources de version.',
+ 'ui.versionIdenticalHint':
+ 'Ces fichiers programme correspondent. Aucune différence de ressources de version à afficher.',
+ 'ui.versionCompareWindowsOnly':
+ "La lecture native des ressources de version n'est disponible que sous Windows. Vous pouvez quand même choisir des chemins ici ; la comparaison expliquera si cette plate-forme ne peut pas les lire.",
'ui.textPatchSourceTextPlaceholder':
"Collez le texte du fichier d'origine pour Appliquer le correctif",
'ui.applyPatchHint': "Prévisualise le texte corrigé en mémoire (n'écrit pas de fichier).",
diff --git a/src/i18n/locales/ja-JP.ts b/src/i18n/locales/ja-JP.ts
index 546f4f93..441636f5 100644
--- a/src/i18n/locales/ja-JP.ts
+++ b/src/i18n/locales/ja-JP.ts
@@ -976,6 +976,13 @@ export const jaJP: LanguagePack = {
'These registry exports match. No key or value differences to show.',
'ui.registryNeedsBothExports': 'Paste both left and right .reg exports first.',
'ui.registryExportPlaceholder': 'Paste a .reg export here',
+ 'ui.versionEmptyCompareHint':
+ 'Choose left and right program files with Browse (or paste paths), then click Compare.',
+ 'ui.versionEmptyReadyHint': 'Press Enter or click Compare to compare these version resources.',
+ 'ui.versionIdenticalHint':
+ 'These program files match. No version-resource differences to show.',
+ 'ui.versionCompareWindowsOnly':
+ 'Native version resource reading is only available on Windows. You can still choose paths here; compare will explain if this platform cannot read them.',
'ui.textPatchSourceTextPlaceholder': 'Paste the original file text for Apply Patch',
'ui.applyPatchHint': 'Preview the patched text in memory (does not write a file).',
'ui.applyToFileHint':
diff --git a/src/i18n/locales/ko-KR.ts b/src/i18n/locales/ko-KR.ts
index a50cf216..cf845be2 100644
--- a/src/i18n/locales/ko-KR.ts
+++ b/src/i18n/locales/ko-KR.ts
@@ -971,6 +971,12 @@ export const koKR: LanguagePack = {
'이 레지스트리 내보내기는 일치합니다. 표시할 키 또는 값 차이가 없습니다.',
'ui.registryNeedsBothExports': '먼저 좌우 .reg 내보내기를 모두 붙여넣으세요.',
'ui.registryExportPlaceholder': '.reg 내보내기를 여기에 붙여넣으세요',
+ 'ui.versionEmptyCompareHint':
+ '찾아보기로 좌우 프로그램 파일을 선택하거나 경로를 붙여넣은 다음 비교를 클릭하세요.',
+ 'ui.versionEmptyReadyHint': 'Enter를 누르거나 비교를 클릭하여 이 버전 리소스를 비교하세요.',
+ 'ui.versionIdenticalHint': '이 프로그램 파일은 일치합니다. 표시할 버전 리소스 차이가 없습니다.',
+ 'ui.versionCompareWindowsOnly':
+ '네이티브 버전 리소스 읽기는 Windows에서만 사용할 수 있습니다. 여기서 경로를 선택할 수는 있으며, 이 플랫폼에서 읽을 수 없으면 비교 시 안내합니다.',
'ui.textPatchSourceTextPlaceholder': '패치 적용을 위해 원본 파일 텍스트를 붙여넣으세요',
'ui.applyPatchHint': '메모리에서 패치된 텍스트를 미리 봅니다(파일에 쓰지 않음).',
'ui.applyToFileHint': '패치 결과를 대상 파일에 씁니다(대상이 비어 있으면 원본에 씀).',
diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts
index 784fdfc9..a95fa59d 100644
--- a/src/i18n/locales/zh-CN.ts
+++ b/src/i18n/locales/zh-CN.ts
@@ -931,6 +931,11 @@ export const zhCN: LanguagePack = {
'ui.registryIdenticalHint': '这些注册表导出一致。没有键或值差异。',
'ui.registryNeedsBothExports': '请先粘贴左右两侧的 .reg 导出。',
'ui.registryExportPlaceholder': '在此粘贴 .reg 导出',
+ 'ui.versionEmptyCompareHint': '用浏览选择左右程序文件(或粘贴路径),然后点比较。',
+ 'ui.versionEmptyReadyHint': '按 Enter 或点比较以比较这些版本资源。',
+ 'ui.versionIdenticalHint': '这些程序文件一致。没有版本资源差异。',
+ 'ui.versionCompareWindowsOnly':
+ '本机版本资源读取仅在 Windows 上可用。仍可在此选择路径;若当前平台无法读取,比较会说明原因。',
'ui.textPatchSourceTextPlaceholder': '粘贴原始文件文本以应用补丁',
'ui.applyPatchHint': '在内存中预览修补后的文本(不写入文件)。',
'ui.applyToFileHint': '将修补结果写入目标文件(若目标为空则写入源文件)。',
diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts
index d54c6610..d3b4c3d0 100644
--- a/src/i18n/locales/zh-TW.ts
+++ b/src/i18n/locales/zh-TW.ts
@@ -933,6 +933,11 @@ export const zhTW: LanguagePack = {
'ui.registryIdenticalHint': '這些登錄檔匯出一致。沒有機碼或值差異。',
'ui.registryNeedsBothExports': '請先貼上左右兩側的 .reg 匯出。',
'ui.registryExportPlaceholder': '在此貼上 .reg 匯出',
+ 'ui.versionEmptyCompareHint': '用瀏覽選擇左右程式檔案(或貼上路徑),然後按比較。',
+ 'ui.versionEmptyReadyHint': '按 Enter 或按比較以比較這些版本資源。',
+ 'ui.versionIdenticalHint': '這些程式檔案一致。沒有版本資源差異。',
+ 'ui.versionCompareWindowsOnly':
+ '原生版本資源讀取僅在 Windows 上可用。仍可在此選擇路徑;若目前平台無法讀取,比較會說明原因。',
'ui.textPatchSourceTextPlaceholder': '貼上原始檔案文字以套用修補',
'ui.applyPatchHint': '在記憶體中預覽修補後的文字(不寫入檔案)。',
'ui.applyToFileHint': '將修補結果寫入目標檔案(若目標為空則寫入來源)。',
diff --git a/src/views/VersionCompareView.test.ts b/src/views/VersionCompareView.test.ts
index 7305ed81..1b124d36 100644
--- a/src/views/VersionCompareView.test.ts
+++ b/src/views/VersionCompareView.test.ts
@@ -1,6 +1,7 @@
import { flushPromises, mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { useSettingsStore } from '@/stores/settings'
+import { usePolicyStore } from '@/stores/policy'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import VersionCompareView from './VersionCompareView.vue'
import { compareVersionFiles, saveTextFile } from '@/api/diff'
@@ -142,6 +143,112 @@ describe('VersionCompareView', () => {
expect(
wrapper.find('[data-testid="version-session-toolbar-rules"]').attributes('disabled'),
).toBeUndefined()
+ expect(wrapper.find('[data-testid="version-empty-hint"]').text()).toContain('Browse')
+ expect(wrapper.find('[data-testid="version-left-path"]').attributes('placeholder')).toContain(
+ 'Browse or paste a file path',
+ )
+ expect(wrapper.find('[data-testid="run-version-compare"]').attributes('title')).toContain(
+ 'left and right files first',
+ )
+ expect(wrapper.find('[data-testid="run-version-compare"]').attributes('disabled')).toBeDefined()
+ expect(wrapper.find('[data-testid="version-windows-only"]').exists()).toBe(true)
+ })
+
+ it('shows a ready hint after both paths are set', async () => {
+ const wrapper = mount(VersionCompareView)
+
+ await wrapper.find('[data-testid="version-left-path"]').setValue('C:/apps/left.exe')
+ await wrapper.find('[data-testid="version-right-path"]').setValue('C:/apps/right.exe')
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.find('[data-testid="version-empty-hint"]').text()).toContain('Compare')
+ expect(
+ wrapper.find('[data-testid="run-version-compare"]').attributes('disabled'),
+ ).toBeUndefined()
+ })
+
+ it('compares version files on Enter when both paths are set', async () => {
+ const wrapper = mount(VersionCompareView)
+
+ await wrapper.find('[data-testid="version-left-path"]').setValue('C:/apps/left.exe')
+ await wrapper.find('[data-testid="version-right-path"]').setValue('C:/apps/right.exe')
+ await wrapper.find('[data-testid="version-right-path"]').trigger('keydown.enter')
+ await flushPromises()
+
+ expect(compareVersionFiles).toHaveBeenCalledWith({
+ leftPath: 'C:/apps/left.exe',
+ rightPath: 'C:/apps/right.exe',
+ })
+ expect(wrapper.find('[data-testid="version-empty-hint"]').exists()).toBe(false)
+ })
+
+ it('shows an identical hint when version resources match', async () => {
+ vi.mocked(compareVersionFiles).mockResolvedValueOnce({
+ left: {
+ name: 'same.exe',
+ fileType: 'Application',
+ targetOs: 'Windows 32-bit',
+ fileVersion: '1.0.0.0',
+ productVersion: '1.0.0.0',
+ },
+ right: {
+ name: 'same.exe',
+ fileType: 'Application',
+ targetOs: 'Windows 32-bit',
+ fileVersion: '1.0.0.0',
+ productVersion: '1.0.0.0',
+ },
+ fields: [
+ {
+ group: 'Fixed Info',
+ field: 'FileVersion',
+ left: '1.0.0.0',
+ right: '1.0.0.0',
+ status: 'unchanged',
+ },
+ ],
+ summary: { added: 0, removed: 0, modified: 0, unchanged: 1 },
+ })
+
+ const wrapper = mount(VersionCompareView)
+
+ await wrapper.find('[data-testid="version-left-path"]').setValue('C:/apps/a.exe')
+ await wrapper.find('[data-testid="version-right-path"]').setValue('C:/apps/b.exe')
+ await wrapper.find('[data-testid="run-version-compare"]').trigger('click')
+ await flushPromises()
+
+ expect(wrapper.find('[data-testid="version-identical-hint"]').text()).toContain('match')
+ })
+
+ it('maps unsupported-platform failures to a plain suggestion', async () => {
+ vi.mocked(compareVersionFiles).mockRejectedValueOnce({
+ code: 'app.unknown',
+ messageKey: 'error.version.unsupportedPlatform.message',
+ suggestionKey: 'error.version.unsupportedPlatform.suggestion',
+ params: {},
+ debugMessage: 'native version resource reading is only available on Windows',
+ })
+
+ const wrapper = mount(VersionCompareView)
+
+ await wrapper.find('[data-testid="version-left-path"]').setValue('C:/apps/a.exe')
+ await wrapper.find('[data-testid="version-right-path"]').setValue('C:/apps/b.exe')
+ await wrapper.find('[data-testid="run-version-compare"]').trigger('click')
+ await flushPromises()
+
+ expect(wrapper.find('[data-testid="version-compare-error"]').text()).toContain(
+ 'only available on Windows',
+ )
+ })
+
+ it('hides the Windows-only note when running on Windows', () => {
+ const policy = usePolicyStore()
+
+ policy.os = 'windows'
+
+ const wrapper = mount(VersionCompareView)
+
+ expect(wrapper.find('[data-testid="version-windows-only"]').exists()).toBe(false)
})
it('filters minor differences and toggles importance rules', async () => {
diff --git a/src/views/VersionCompareView.vue b/src/views/VersionCompareView.vue
index b1598581..8d04f95e 100644
--- a/src/views/VersionCompareView.vue
+++ b/src/views/VersionCompareView.vue
@@ -35,8 +35,10 @@ import { useSessionLaunchStore } from '@/stores/sessionLaunch'
import { useViewActionsStore } from '@/stores/viewActions'
import { useTabsStore } from '@/stores/tabs'
import { useSettingsStore } from '@/stores/settings'
+import { usePolicyStore } from '@/stores/policy'
const settings = useSettingsStore()
+const policy = usePolicyStore()
const versionStatuses: VersionFieldStatus[] = ['added', 'removed', 'modified', 'unchanged']
const emptyVersionSide: VersionSideSummary = {
@@ -156,6 +158,32 @@ const minorDifferenceCount = computed(
).length,
)
+const compareNeedsBothFiles = computed(() => !leftPath.value.trim() || !rightPath.value.trim())
+const hasVersionResult = computed(() => versionFields.value.length > 0)
+const versionsAreIdentical = computed(() => {
+ if (!hasVersionResult.value) {
+ return false
+ }
+
+ const summary = versionSummary.value
+
+ return summary.added === 0 && summary.removed === 0 && summary.modified === 0
+})
+const versionEmptyStateMessage = computed(() => {
+ if (hasVersionResult.value) {
+ return ''
+ }
+
+ if (leftPath.value.trim() && rightPath.value.trim()) {
+ return t('ui.versionEmptyReadyHint')
+ }
+
+ return t('ui.versionEmptyCompareHint')
+})
+const compareVersionTip = computed(() =>
+ compareNeedsBothFiles.value ? t('ui.compareNeedsBothFiles') : t('ui.runDiff'),
+)
+
const versionSessionToolbar = computed(() =>
buildVersionCompareToolbar({
home: true,
@@ -544,22 +572,70 @@ async function exportVersionReport(): Promise
+ {{ $t('ui.versionIdenticalHint') }} +
++ {{ versionEmptyStateMessage }} +
++ {{ $t('ui.versionCompareWindowsOnly') }} +