mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 21:28:25 +08:00
chore: updated template displays to match the system language (#35629)
This commit is contained in:
parent
0b6bef394a
commit
6eb8433282
@ -46,7 +46,7 @@ export function ViewMoreButton({ searchParams, searchTab }: ViewMoreButtonProps)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="system-xs-medium flex cursor-pointer items-center text-text-accent"
|
||||
className="flex cursor-pointer items-center text-text-accent system-xs-medium"
|
||||
onClick={() => onMoreClick(searchParams, searchTab)}
|
||||
>
|
||||
{t('marketplace.viewMore', { ns: 'plugin' })}
|
||||
@ -72,14 +72,23 @@ export function CollectionHeader<TCollection extends BaseCollection>({
|
||||
&& !!collection.search_params
|
||||
&& itemsLength > GRID_DISPLAY_LIMIT
|
||||
|
||||
// The API only ships translations for a subset of locales (typically en_US
|
||||
// and zh_Hans). For any other locale (e.g. ja_JP, pt_BR) the keyed lookup
|
||||
// returns undefined and the title/description render as empty divs. Fall
|
||||
// back to the en_US translation, then to whatever value is available, so
|
||||
// the header always shows something meaningful.
|
||||
const lang = getLanguage(locale)
|
||||
const label = collection.label[lang] || collection.label.en_US || Object.values(collection.label)[0] || ''
|
||||
const description = collection.description[lang] || collection.description.en_US || Object.values(collection.description)[0] || ''
|
||||
|
||||
return (
|
||||
<div className="mb-2 flex items-end justify-between">
|
||||
<div>
|
||||
<div className="title-xl-semi-bold text-text-primary">
|
||||
{collection.label[getLanguage(locale)]}
|
||||
<div className="text-text-primary title-xl-semi-bold">
|
||||
{label}
|
||||
</div>
|
||||
<div className="system-xs-regular text-text-tertiary">
|
||||
{collection.description[getLanguage(locale)]}
|
||||
<div className="text-text-tertiary system-xs-regular">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
{showViewMore && viewMore}
|
||||
|
||||
@ -6,10 +6,7 @@ export type LanguageOption = {
|
||||
|
||||
export const LANGUAGE_OPTIONS: LanguageOption[] = [
|
||||
{ value: 'en', label: 'English', nativeLabel: 'English' },
|
||||
{ value: 'zh-Hans', label: 'Simplified Chinese', nativeLabel: '简体中文' },
|
||||
{ value: 'zh-Hant', label: 'Traditional Chinese', nativeLabel: '繁體中文' },
|
||||
{ value: 'zh-Hans', label: 'Simplified Chinese', nativeLabel: '中文' },
|
||||
{ value: 'ja', label: 'Japanese', nativeLabel: '日本語' },
|
||||
{ value: 'es', label: 'Spanish', nativeLabel: 'Español' },
|
||||
{ value: 'fr', label: 'French', nativeLabel: 'Français' },
|
||||
{ value: 'ko', label: 'Korean', nativeLabel: '한국어' },
|
||||
{ value: 'other', label: 'Other', nativeLabel: 'Other' },
|
||||
]
|
||||
|
||||
@ -6,6 +6,7 @@ import { CATEGORY_ALL } from './constants'
|
||||
import { useMarketplaceContainerScroll } from './hooks'
|
||||
import { useMarketplaceCollectionsAndPlugins, useMarketplacePlugins, useMarketplaceTemplateCollectionsAndTemplates, useMarketplaceTemplates } from './query'
|
||||
import { CREATION_TYPE } from './search-params'
|
||||
import { useTemplateCollectionsMapFilteredBySystemLanguage, useTemplatesFilteredBySystemLanguage } from './use-sync-system-language'
|
||||
import { getCollectionsParams, getPluginFilterType, mapTemplateDetailToTemplate } from './utils'
|
||||
|
||||
const getCategory = (category: string) => {
|
||||
@ -112,10 +113,31 @@ export function useTemplatesMarketplaceData(enabled = true) {
|
||||
// Scroll pagination
|
||||
useMarketplaceContainerScroll(handlePageChange)
|
||||
|
||||
// Raw templates as returned by the API (after pagination/flattening).
|
||||
const rawTemplates = useMemo(
|
||||
() => templatesQuery.data?.pages.flatMap(page => page.templates).map(mapTemplateDetailToTemplate),
|
||||
[templatesQuery.data],
|
||||
)
|
||||
|
||||
// If the "Filter by language" dropdown is empty, post-filter the returned
|
||||
// templates by the user's system language. If the dropdown has any picks,
|
||||
// pass the list through untouched (the server has already filtered).
|
||||
//
|
||||
// The same rule applies to the curated-collections layout, which is what
|
||||
// the page renders in default mode (no search, no category, no manual
|
||||
// filter) — without this, the system-language filter never visibly takes
|
||||
// effect on first entry.
|
||||
const hasManualFilter = filterTemplateLanguages.length > 0
|
||||
const templates = useTemplatesFilteredBySystemLanguage(rawTemplates, hasManualFilter)
|
||||
const templateCollectionTemplatesMap = useTemplateCollectionsMapFilteredBySystemLanguage(
|
||||
templateCollectionsQuery.data?.templateCollectionTemplatesMap,
|
||||
hasManualFilter,
|
||||
)
|
||||
|
||||
return {
|
||||
templateCollections: templateCollectionsQuery.data?.templateCollections,
|
||||
templateCollectionTemplatesMap: templateCollectionsQuery.data?.templateCollectionTemplatesMap,
|
||||
templates: templatesQuery.data?.pages.flatMap(page => page.templates).map(mapTemplateDetailToTemplate),
|
||||
templateCollectionTemplatesMap,
|
||||
templates,
|
||||
templatesTotal: templatesQuery.data?.pages[0]?.total,
|
||||
page: templatesQuery.data?.pages.length || 1,
|
||||
isLoading: templateCollectionsQuery.isLoading || templatesQuery.isLoading,
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
import type { Template } from './types'
|
||||
import { useMemo } from 'react'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
|
||||
export type TemplateLanguageBucket = 'en' | 'zh' | 'ja' | 'other'
|
||||
|
||||
/**
|
||||
* Map the user's system locale to one of the four template-language buckets
|
||||
* exposed by LANGUAGE_OPTIONS:
|
||||
*
|
||||
* - en-* (e.g. en-US) → 'en'
|
||||
* - zh-Hans, zh-Hant, zh-* → 'zh'
|
||||
* - ja-* (e.g. ja-JP) → 'ja'
|
||||
* - anything else → 'other'
|
||||
*/
|
||||
export function localeToLanguageBucket(locale: string): TemplateLanguageBucket {
|
||||
const lower = locale.toLowerCase()
|
||||
if (lower.startsWith('en'))
|
||||
return 'en'
|
||||
if (lower.startsWith('zh'))
|
||||
return 'zh'
|
||||
if (lower.startsWith('ja'))
|
||||
return 'ja'
|
||||
return 'other'
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a template's `preferred_languages` belong to a given bucket.
|
||||
*
|
||||
* - 'en' bucket: at least one entry starts with 'en'
|
||||
* - 'zh' bucket: at least one entry starts with 'zh'
|
||||
* - 'ja' bucket: at least one entry starts with 'ja'
|
||||
* - 'other' bucket: NONE of the entries start with en / zh / ja (this also
|
||||
* matches templates that have an empty preferred_languages array)
|
||||
*/
|
||||
export function templateMatchesBucket(
|
||||
template: Pick<Template, 'preferred_languages'>,
|
||||
bucket: TemplateLanguageBucket,
|
||||
): boolean {
|
||||
const langs = (template.preferred_languages || []).map(l => l.toLowerCase())
|
||||
|
||||
if (bucket === 'other') {
|
||||
return !langs.some(l =>
|
||||
l.startsWith('en') || l.startsWith('zh') || l.startsWith('ja'),
|
||||
)
|
||||
}
|
||||
|
||||
return langs.some(l => l.startsWith(bucket))
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the templates list by the user's system language when they have
|
||||
* NOT manually selected a language in the "Filter by language" dropdown.
|
||||
*
|
||||
* - When `hasManualFilter` is true, the server has already filtered the
|
||||
* list, so the input is returned untouched.
|
||||
* - Otherwise, the list is filtered client-side by the bucket derived
|
||||
* from the current system locale. Changing the system locale causes
|
||||
* this hook to re-compute and the visible list refreshes — but the
|
||||
* templates `search/advanced` API is NOT refired (its queryKey doesn't
|
||||
* depend on the locale).
|
||||
*/
|
||||
export function useTemplatesFilteredBySystemLanguage<
|
||||
T extends Pick<Template, 'preferred_languages'>,
|
||||
>(
|
||||
templates: T[] | undefined,
|
||||
hasManualFilter: boolean,
|
||||
): T[] | undefined {
|
||||
const locale = useLocale()
|
||||
return useMemo(() => {
|
||||
if (!templates)
|
||||
return undefined
|
||||
if (hasManualFilter)
|
||||
return templates
|
||||
const bucket = localeToLanguageBucket(locale)
|
||||
return templates.filter(t => templateMatchesBucket(t, bucket))
|
||||
}, [templates, hasManualFilter, locale])
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `useTemplatesFilteredBySystemLanguage`, but for the collection →
|
||||
* templates map used by the curated-collections layout. Each collection's
|
||||
* template list is filtered independently.
|
||||
*
|
||||
* Empty collections (where every template was filtered out) are kept in
|
||||
* the map; the consumer can decide whether to hide them.
|
||||
*/
|
||||
export function useTemplateCollectionsMapFilteredBySystemLanguage<
|
||||
T extends Pick<Template, 'preferred_languages'>,
|
||||
>(
|
||||
map: Record<string, T[]> | undefined,
|
||||
hasManualFilter: boolean,
|
||||
): Record<string, T[]> | undefined {
|
||||
const locale = useLocale()
|
||||
return useMemo(() => {
|
||||
if (!map)
|
||||
return undefined
|
||||
if (hasManualFilter)
|
||||
return map
|
||||
const bucket = localeToLanguageBucket(locale)
|
||||
const filtered: Record<string, T[]> = {}
|
||||
for (const key of Object.keys(map))
|
||||
filtered[key] = map[key].filter(t => templateMatchesBucket(t, bucket))
|
||||
return filtered
|
||||
}, [map, hasManualFilter, locale])
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user