mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-02 16:32:14 +00:00
fix(frontend): 补齐 en 语言包缺失 key 并对 site_logo 统一应用 sanitizeUrl
This commit is contained in:
1 parent
0438057c0b
commit
15c59be787
7 files changed
+82
-4
No files matched your search
@@ -194,6 +194,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { useAdminSettingsStore, useAppStore, useAuthStore, useOnboardingStore } from '@/stores'
|
import { useAdminSettingsStore, useAppStore, useAuthStore, useOnboardingStore } from '@/stores'
|
||||||
import VersionBadge from '@/components/common/VersionBadge.vue'
|
import VersionBadge from '@/components/common/VersionBadge.vue'
|
||||||
import { sanitizeSvg } from '@/utils/sanitize'
|
import { sanitizeSvg } from '@/utils/sanitize'
|
||||||
|
import { sanitizeUrl } from '@/utils/url'
|
||||||
import { FeatureFlags, makeSidebarFlag } from '@/utils/featureFlags'
|
import { FeatureFlags, makeSidebarFlag } from '@/utils/featureFlags'
|
||||||
import { useBatchImageAccess } from '@/composables/useBatchImageAccess'
|
import { useBatchImageAccess } from '@/composables/useBatchImageAccess'
|
||||||
|
|
||||||
@@ -256,7 +257,7 @@ const expandedGroups = ref<Set<string>>(new Set())
|
|||||||
|
|
||||||
// Site settings from appStore (cached, no flicker)
|
// Site settings from appStore (cached, no flicker)
|
||||||
const siteName = computed(() => appStore.siteName)
|
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 siteVersion = computed(() => appStore.siteVersion)
|
||||||
const settingsLoaded = computed(() => appStore.publicSettingsLoaded)
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -23,6 +23,7 @@ export default {
|
|||||||
lastRun: 'last_run:',
|
lastRun: 'last_run:',
|
||||||
lastSuccess: 'last_success:',
|
lastSuccess: 'last_success:',
|
||||||
lastError: 'last_error:',
|
lastError: 'last_error:',
|
||||||
|
result: 'Result',
|
||||||
noData: 'No data.',
|
noData: 'No data.',
|
||||||
loadingText: 'loading',
|
loadingText: 'loading',
|
||||||
ready: 'ready',
|
ready: 'ready',
|
||||||
@@ -143,7 +144,12 @@ export default {
|
|||||||
'6h': 'Last 6 hours',
|
'6h': 'Last 6 hours',
|
||||||
'24h': 'Last 24 hours',
|
'24h': 'Last 24 hours',
|
||||||
'7d': 'Last 7 days',
|
'7d': 'Last 7 days',
|
||||||
'30d': 'Last 30 days'
|
'30d': 'Last 30 days',
|
||||||
|
custom: 'Custom Range'
|
||||||
|
},
|
||||||
|
customTimeRange: {
|
||||||
|
startTime: 'Start Time',
|
||||||
|
endTime: 'End Time'
|
||||||
},
|
},
|
||||||
openaiTokenStats: {
|
openaiTokenStats: {
|
||||||
title: 'OpenAI Token Request Stats',
|
title: 'OpenAI Token Request Stats',
|
||||||
|
|||||||
@@ -770,6 +770,7 @@ export default {
|
|||||||
failedToLoad: 'Failed to load groups',
|
failedToLoad: 'Failed to load groups',
|
||||||
failedToCreate: 'Failed to create group',
|
failedToCreate: 'Failed to create group',
|
||||||
failedToUpdate: 'Failed to update group',
|
failedToUpdate: 'Failed to update group',
|
||||||
|
failedToSave: 'Failed to save group',
|
||||||
failedToDelete: 'Failed to delete group',
|
failedToDelete: 'Failed to delete group',
|
||||||
nameRequired: 'Please enter group name',
|
nameRequired: 'Please enter group name',
|
||||||
rateMultipliers: 'Rate Multipliers',
|
rateMultipliers: 'Rate Multipliers',
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
// Site settings - directly from appStore (already initialized from injected config)
|
// Site settings - directly from appStore (already initialized from injected config)
|
||||||
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
|
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 siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'AI API Gateway Platform')
|
||||||
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
|
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
|
||||||
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
|
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ const appStore = useAppStore()
|
|||||||
// ==================== Site Settings (same as HomeView) ====================
|
// ==================== Site Settings (same as HomeView) ====================
|
||||||
|
|
||||||
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
|
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 docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
|
||||||
const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
|
const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user