fix: use localized doc pathMap links for multilingual anchors

This commit is contained in:
yyh
2026-02-13 11:30:36 +08:00
parent eac9cbdfb9
commit 87d09b2e3d
5 changed files with 38 additions and 83 deletions

View File

@ -1,15 +0,0 @@
import type { DocAnchorMap } from './i18n'
export const appManagementAnchorMap = {
'zh-Hans': '应用导出和导入',
'zh_Hans': '应用导出和导入',
'ja-JP': 'アプリのエクスポートとインポート',
'ja_JP': 'アプリのエクスポートとインポート',
} satisfies DocAnchorMap
export const fileSystemArtifactsAnchorMap = {
'zh-Hans': '产物',
'zh_Hans': '产物',
'ja-JP': 'アーティファクト',
'ja_JP': 'アーティファクト',
} satisfies DocAnchorMap

View File

@ -1,4 +1,4 @@
import type { DocAnchorMap, DocPathMap } from './i18n'
import type { DocPathMap } from './i18n'
import type { DocPathWithoutLang } from '@/types/doc-paths'
import { useTranslation } from '#i18n'
import { renderHook } from '@testing-library/react'
@ -103,7 +103,7 @@ describe('useDocLink', () => {
}
const { result } = renderHook(() => useDocLink())
const url = result.current('/use-dify/getting-started/quick-start' as DocPathWithoutLang, { pathMap })
const url = result.current('/use-dify/getting-started/quick-start' as DocPathWithoutLang, pathMap)
expect(url).toBe(`${defaultDocBaseUrl}/zh/use-dify/getting-started/introduction`)
})
@ -119,7 +119,7 @@ describe('useDocLink', () => {
}
const { result } = renderHook(() => useDocLink())
const url = result.current('/use-dify/getting-started/quick-start' as DocPathWithoutLang, { pathMap })
const url = result.current('/use-dify/getting-started/quick-start' as DocPathWithoutLang, pathMap)
expect(url).toBe(`${defaultDocBaseUrl}/ja/use-dify/getting-started/quick-start`)
})
@ -234,17 +234,6 @@ describe('useDocLink', () => {
const url = result.current('/use-dify/getting-started/introduction')
expect(url).toBe(`${defaultDocBaseUrl}/zh/use-dify/getting-started/introduction`)
})
it('should preserve anchor while translating API reference path', () => {
vi.mocked(useTranslation).mockReturnValue({
i18n: { language: 'zh-Hans' },
} as ReturnType<typeof useTranslation>)
vi.mocked(getDocLanguage).mockReturnValue('zh')
const { result } = renderHook(() => useDocLink())
const url = result.current('/api-reference/annotations/create-annotation#request-body')
expect(url).toBe(`${defaultDocBaseUrl}/api-reference/标注管理/创建标注#request-body`)
})
})
describe('Edge Cases', () => {
@ -254,20 +243,22 @@ describe('useDocLink', () => {
expect(url).toBe(`${defaultDocBaseUrl}/en/use-dify/getting-started/introduction#overview`)
})
it('should support locale-specific anchors via anchorMap', () => {
it('should support locale-specific anchors via pathMap', () => {
vi.mocked(useTranslation).mockReturnValue({
i18n: { language: 'zh-Hans' },
} as ReturnType<typeof useTranslation>)
vi.mocked(getDocLanguage).mockReturnValue('zh')
const anchorMap: DocAnchorMap = {
'zh-Hans': '应用导出和导入',
'ja-JP': 'アプリのエクスポートとインポート',
const pathMap: DocPathMap = {
'zh-Hans': '/use-dify/workspace/app-management#应用导出和导入' as DocPathWithoutLang,
'zh_Hans': '/use-dify/workspace/app-management#应用导出和导入' as DocPathWithoutLang,
'ja-JP': '/use-dify/workspace/app-management#アプリのエクスポートとインポート' as DocPathWithoutLang,
'ja_JP': '/use-dify/workspace/app-management#アプリのエクスポートとインポート' as DocPathWithoutLang,
}
const { result } = renderHook(() => useDocLink())
const url = result.current('/use-dify/workspace/app-management#app-export-and-import', { anchorMap })
expect(url).toBe(`${defaultDocBaseUrl}/zh/use-dify/workspace/app-management#${encodeURIComponent('应用导出和导入')}`)
const url = result.current('/use-dify/workspace/app-management#app-export-and-import', pathMap)
expect(url).toBe(`${defaultDocBaseUrl}/zh/use-dify/workspace/app-management#应用导出和导入`)
})
it('should handle multiple calls with same hook instance', () => {

View File

@ -1,7 +1,7 @@
import type { Locale } from '@/i18n-config/language'
import type { DocPathWithoutLang } from '@/types/doc-paths'
import { useTranslation } from '#i18n'
import { useCallback, useMemo } from 'react'
import { useCallback } from 'react'
import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n-config/language'
import { apiReferencePathTranslations } from '@/types/doc-paths'
@ -23,63 +23,29 @@ export const useGetPricingPageLanguage = () => {
export const defaultDocBaseUrl = 'https://docs.bash-is-all-you-need.dify.dev'
export type DocPathMap = Partial<Record<Locale, DocPathWithoutLang>>
export type DocAnchorMap = Partial<Record<Locale, string>>
export type DocLinkOptions = {
pathMap?: DocPathMap
anchorMap?: DocAnchorMap
}
export type BuildDocLink = (path?: DocPathWithoutLang, options?: DocLinkOptions) => string
const splitPathWithHash = (path: string) => {
const [pathname, ...hashParts] = path.split('#')
return {
pathname,
hash: hashParts.join('#'),
}
}
const normalizeAnchor = (anchor: string) => {
const normalizedAnchor = anchor.startsWith('#') ? anchor.slice(1) : anchor
if (!normalizedAnchor)
return ''
const isAsciiOnly = Array.from(normalizedAnchor).every(char => char.codePointAt(0)! <= 0x7F)
if (isAsciiOnly)
return normalizedAnchor
return encodeURIComponent(normalizedAnchor)
}
export const useDocLink = (baseUrl?: string): BuildDocLink => {
const baseDocUrl = useMemo(() => {
const resolvedBaseUrl = baseUrl || defaultDocBaseUrl
return resolvedBaseUrl.endsWith('/') ? resolvedBaseUrl.slice(0, -1) : resolvedBaseUrl
}, [baseUrl])
export const useDocLink = (baseUrl?: string): ((path?: DocPathWithoutLang, pathMap?: DocPathMap) => string) => {
let baseDocUrl = baseUrl || defaultDocBaseUrl
baseDocUrl = (baseDocUrl.endsWith('/')) ? baseDocUrl.slice(0, -1) : baseDocUrl
const locale = useLocale()
return useCallback(
(path?: DocPathWithoutLang, options?: DocLinkOptions): string => {
(path?: DocPathWithoutLang, pathMap?: DocPathMap): string => {
const docLanguage = getDocLanguage(locale)
const pathUrl = path || ''
const { pathMap, anchorMap } = options || {}
const targetPath = (pathMap) ? pathMap[locale] || pathUrl : pathUrl
const { pathname: pathWithoutHash, hash: pathAnchor } = splitPathWithHash(targetPath)
let targetPathWithoutHash = pathWithoutHash
let targetPath = (pathMap) ? pathMap[locale] || pathUrl : pathUrl
let languagePrefix = `/${docLanguage}`
if (targetPathWithoutHash.startsWith('/api-reference/')) {
if (targetPath.startsWith('/api-reference/')) {
languagePrefix = ''
if (docLanguage !== 'en') {
const translatedPath = apiReferencePathTranslations[targetPathWithoutHash]?.[docLanguage]
const translatedPath = apiReferencePathTranslations[targetPath]?.[docLanguage]
if (translatedPath) {
targetPathWithoutHash = translatedPath
targetPath = translatedPath
}
}
}
const anchor = normalizeAnchor(anchorMap?.[locale] || pathAnchor)
const anchorSuffix = anchor ? `#${anchor}` : ''
return `${baseDocUrl}${languagePrefix}${targetPathWithoutHash}${anchorSuffix}`
return `${baseDocUrl}${languagePrefix}${targetPath}`
},
[baseDocUrl, locale],
)