Merge pull request #3864 from fengshao1227/fix/frontend-i18n-and-site-logo-sanitize

fix(frontend): 补齐 en 语言包缺失 key 并对 site_logo 统一应用 sanitizeUrl
This commit is contained in:
Wesley Liddick
2026-07-09 17:35:00 +08:00
committed by GitHub
7 changed files with 82 additions and 4 deletions
@@ -194,6 +194,7 @@ import { useI18n } from 'vue-i18n'
import { useAdminSettingsStore, useAppStore, useAuthStore, useOnboardingStore } from '@/stores'
import VersionBadge from '@/components/common/VersionBadge.vue'
import { sanitizeSvg } from '@/utils/sanitize'
import { sanitizeUrl } from '@/utils/url'
import { FeatureFlags, makeSidebarFlag } from '@/utils/featureFlags'
import { useBatchImageAccess } from '@/composables/useBatchImageAccess'
@@ -256,7 +257,7 @@ const expandedGroups = ref<Set<string>>(new Set())
// Site settings from appStore (cached, no flicker)
const siteName = computed(() => appStore.siteName)
const siteLogo = computed(() => appStore.siteLogo)
const siteLogo = computed(() => sanitizeUrl(appStore.siteLogo || '', { allowRelative: true, allowDataUrl: true }))
const siteVersion = computed(() => appStore.siteVersion)
const settingsLoaded = computed(() => appStore.publicSettingsLoaded)
@@ -0,0 +1,32 @@
import { readFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
const dir = dirname(fileURLToPath(import.meta.url))
const sidebarSource = readFileSync(resolve(dir, '../AppSidebar.vue'), 'utf8')
const homeViewSource = readFileSync(resolve(dir, '../../../views/HomeView.vue'), 'utf8')
const keyUsageViewSource = readFileSync(resolve(dir, '../../../views/KeyUsageView.vue'), 'utf8')
describe('site_logo sanitization', () => {
it('AppSidebar imports sanitizeUrl and applies it to siteLogo', () => {
expect(sidebarSource).toContain("import { sanitizeUrl } from '@/utils/url'")
expect(sidebarSource).toContain('sanitizeUrl(appStore.siteLogo')
})
it('HomeView applies sanitizeUrl to siteLogo', () => {
expect(homeViewSource).toContain('sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo')
})
it('KeyUsageView applies sanitizeUrl to siteLogo', () => {
expect(keyUsageViewSource).toContain('sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo')
})
it('all three pass allowRelative and allowDataUrl options', () => {
for (const src of [sidebarSource, homeViewSource, keyUsageViewSource]) {
expect(src).toContain('allowRelative: true')
expect(src).toContain('allowDataUrl: true')
}
})
})
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest'
import en from '@/i18n/locales/en'
function flattenKeys(obj: Record<string, any>, prefix = ''): string[] {
const keys: string[] = []
for (const [k, v] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${k}` : k
if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
keys.push(...flattenKeys(v, fullKey))
} else {
keys.push(fullKey)
}
}
return keys
}
describe('ops locale key completeness', () => {
const requiredKeys = [
'admin.ops.result',
'admin.ops.timeRange.custom',
'admin.ops.customTimeRange.startTime',
'admin.ops.customTimeRange.endTime',
]
for (const key of requiredKeys) {
it(`en locale has ${key}`, () => {
const enKeys = flattenKeys(en)
expect(enKeys).toContain(key)
})
}
})
describe('groups locale key completeness', () => {
it('en locale has admin.groups.failedToSave', () => {
const enKeys = flattenKeys(en)
expect(enKeys).toContain('admin.groups.failedToSave')
})
})
+7 -1
View File
@@ -23,6 +23,7 @@ export default {
lastRun: 'last_run:',
lastSuccess: 'last_success:',
lastError: 'last_error:',
result: 'Result',
noData: 'No data.',
loadingText: 'loading',
ready: 'ready',
@@ -143,7 +144,12 @@ export default {
'6h': 'Last 6 hours',
'24h': 'Last 24 hours',
'7d': 'Last 7 days',
'30d': 'Last 30 days'
'30d': 'Last 30 days',
custom: 'Custom Range'
},
customTimeRange: {
startTime: 'Start Time',
endTime: 'End Time'
},
openaiTokenStats: {
title: 'OpenAI Token Request Stats',
@@ -770,6 +770,7 @@ export default {
failedToLoad: 'Failed to load groups',
failedToCreate: 'Failed to create group',
failedToUpdate: 'Failed to update group',
failedToSave: 'Failed to save group',
failedToDelete: 'Failed to delete group',
nameRequired: 'Please enter group name',
rateMultipliers: 'Rate Multipliers',
+1 -1
View File
@@ -419,7 +419,7 @@ const appStore = useAppStore()
// Site settings - directly from appStore (already initialized from injected config)
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
const siteLogo = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '', { allowRelative: true, allowDataUrl: true }))
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'AI API Gateway Platform')
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
+1 -1
View File
@@ -431,7 +431,7 @@ const appStore = useAppStore()
// ==================== Site Settings (same as HomeView) ====================
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
const siteLogo = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '', { allowRelative: true, allowDataUrl: true }))
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
const githubUrl = 'https://github.com/Wei-Shaw/sub2api'