This commit is contained in:
Stephen Zhou 2025-12-29 13:21:16 +08:00
parent 6c781571c8
commit ca9c77f95b
No known key found for this signature in database
8 changed files with 75 additions and 18 deletions

View File

@ -10,6 +10,7 @@ import { usePathname } from 'next/navigation'
import * as React from 'react' import * as React from 'react'
import { Fragment } from 'react' import { Fragment } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { replace } from 'string-ts'
import AudioBtn from '@/app/components/base/audio-btn' import AudioBtn from '@/app/components/base/audio-btn'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
@ -100,7 +101,7 @@ const VoiceParamConfig = ({
className="h-full w-full cursor-pointer rounded-lg border-0 bg-components-input-bg-normal py-1.5 pl-3 pr-10 focus-visible:bg-state-base-hover focus-visible:outline-none group-hover:bg-state-base-hover sm:text-sm sm:leading-6" className="h-full w-full cursor-pointer rounded-lg border-0 bg-components-input-bg-normal py-1.5 pl-3 pr-10 focus-visible:bg-state-base-hover focus-visible:outline-none group-hover:bg-state-base-hover sm:text-sm sm:leading-6"
> >
<span className={cn('block truncate text-left text-text-secondary', !languageItem?.name && 'text-text-tertiary')}> <span className={cn('block truncate text-left text-text-secondary', !languageItem?.name && 'text-text-tertiary')}>
{languageItem?.name ? t(`voice.language.${languageItem?.value.replace('-', '')}` as VoiceLanguageKey, { ns: 'common' }) : localLanguagePlaceholder} {languageItem?.name ? t(`voice.language.${replace(languageItem?.value, '-', '')}`, { ns: 'common' }) : localLanguagePlaceholder}
</span> </span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronDownIcon <ChevronDownIcon
@ -119,7 +120,7 @@ const VoiceParamConfig = ({
<ListboxOptions <ListboxOptions
className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg px-1 py-1 text-base shadow-lg focus:outline-none sm:text-sm" className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg px-1 py-1 text-base shadow-lg focus:outline-none sm:text-sm"
> >
{languages.map((item: Item) => ( {languages.map(item => (
<ListboxOption <ListboxOption
key={item.value} key={item.value}
className="relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover data-[active]:bg-state-base-active" className="relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover data-[active]:bg-state-base-active"
@ -131,7 +132,7 @@ const VoiceParamConfig = ({
<span <span
className={cn('block', selected && 'font-normal')} className={cn('block', selected && 'font-normal')}
> >
{t(`voice.language.${(item.value).toString().replace('-', '')}` as VoiceLanguageKey, { ns: 'common' })} {t(`voice.language.${replace((item.value), '-', '')}`, { ns: 'common' })}
</span> </span>
{(selected || item.value === text2speech?.language) && ( {(selected || item.value === text2speech?.language) && (
<span <span

View File

@ -15,11 +15,15 @@ import { InputTypeEnum } from './types'
type VariableConfigKeySuffix = I18nKeysByPrefix<'appDebug', 'variableConfig.'> type VariableConfigKeySuffix = I18nKeysByPrefix<'appDebug', 'variableConfig.'>
const i18nFileTypeMap: Partial<Record<InputType, VariableConfigKeySuffix>> = { const i18nFileTypeMap = {
'text-input': 'text-input',
'paragraph': 'paragraph',
'number': 'number', 'number': 'number',
'select': 'select',
'checkbox': 'checkbox',
'file': 'single-file', 'file': 'single-file',
'file-list': 'multi-files', 'file-list': 'multi-files',
} } satisfies Record<InputType, VariableConfigKeySuffix>
const INPUT_TYPE_ICON = { const INPUT_TYPE_ICON = {
[PipelineInputVarType.textInput]: RiTextSnippet, [PipelineInputVarType.textInput]: RiTextSnippet,
@ -48,7 +52,7 @@ export const useInputTypeOptions = (supportFile: boolean) => {
return options.map((value) => { return options.map((value) => {
return { return {
value, value,
label: t(`variableConfig.${i18nFileTypeMap[value] || value}` as `variableConfig.${VariableConfigKeySuffix}`, { ns: 'appDebug' }), label: t(`variableConfig.${i18nFileTypeMap[value]}`, { ns: 'appDebug' }),
Icon: INPUT_TYPE_ICON[value], Icon: INPUT_TYPE_ICON[value],
type: DATA_TYPE[value], type: DATA_TYPE[value],
} }

View File

@ -1,5 +1,4 @@
import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store' import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node' import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node'
@ -43,8 +42,8 @@ export const useAvailableNodesMetaData = () => {
const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => { const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => {
const { metaData } = node const { metaData } = node
const title = t(`blocks.${metaData.type}` as I18nKeysWithPrefix<'workflow', 'blocks.'>, { ns: 'workflow' }) const title = t(`blocks.${metaData.type}`, { ns: 'workflow' })
const description = t(`blocksAbout.${metaData.type}` as I18nKeysWithPrefix<'workflow', 'blocksAbout.'>, { ns: 'workflow' }) const description = t(`blocksAbout.${metaData.type}`, { ns: 'workflow' })
const helpLinkPath = `guides/workflow/node/${metaData.helpLinkUri}` const helpLinkPath = `guides/workflow/node/${metaData.helpLinkUri}`
return { return {
...node, ...node,

View File

@ -238,7 +238,10 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
list.push({ list.push({
id: `${type}-need-added`, id: `${type}-need-added`,
type, type,
// We don't have enough type info for t() here
title: t(`blocks.${type}` as I18nKeysWithPrefix<'workflow', 'blocks.'>, { ns: 'workflow' }), title: t(`blocks.${type}` as I18nKeysWithPrefix<'workflow', 'blocks.'>, { ns: 'workflow' }),
errorMessage: t('common.needAdd', { ns: 'workflow', node: t(`blocks.${type}` as I18nKeysWithPrefix<'workflow', 'blocks.'>, { ns: 'workflow' }) }), errorMessage: t('common.needAdd', { ns: 'workflow', node: t(`blocks.${type}` as I18nKeysWithPrefix<'workflow', 'blocks.'>, { ns: 'workflow' }) }),
canNavigate: false, canNavigate: false,
}) })

View File

@ -3,15 +3,32 @@ export default {
meta: { meta: {
type: 'problem', type: 'problem',
docs: { docs: {
description: 'Disallow using "as any" type assertion in t() function calls', description: 'Disallow using type assertions in t() function calls',
}, },
schema: [], schema: [
{
type: 'object',
properties: {
mode: {
type: 'string',
enum: ['any', 'all'],
default: 'any',
},
},
additionalProperties: false,
},
],
messages: { messages: {
noAsAnyInT: noAsAnyInT:
'Avoid using "as any" in t() function calls. Use proper i18n key types instead.', 'Avoid using "as any" in t() function calls. Use proper i18n key types instead.',
noAsInT:
'Avoid using type assertions in t() function calls. Use proper i18n key types instead.',
}, },
}, },
create(context) { create(context) {
const options = context.options[0] || {}
const mode = options.mode || 'any'
/** /**
* Check if this is a t() function call * Check if this is a t() function call
* @param {import('estree').CallExpression} node * @param {import('estree').CallExpression} node
@ -45,6 +62,23 @@ export default {
) )
} }
/**
* Check if a node is a TSAsExpression (excluding "as const")
* @param {object} node
* @returns {boolean}
*/
function isAsExpression(node) {
if (node.type !== 'TSAsExpression')
return false
// Ignore "as const"
if (node.typeAnnotation && node.typeAnnotation.type === 'TSTypeReference') {
const typeName = node.typeAnnotation.typeName
if (typeName && typeName.type === 'Identifier' && typeName.name === 'const')
return false
}
return true
}
return { return {
CallExpression(node) { CallExpression(node) {
if (!isTCall(node) || node.arguments.length === 0) if (!isTCall(node) || node.arguments.length === 0)
@ -52,12 +86,23 @@ export default {
const firstArg = node.arguments[0] const firstArg = node.arguments[0]
// Check if the first argument uses "as any" if (mode === 'all') {
if (isAsAny(firstArg)) { // Check for any type assertion
context.report({ if (isAsExpression(firstArg)) {
node: firstArg, context.report({
messageId: 'noAsAnyInT', node: firstArg,
}) messageId: 'noAsInT',
})
}
}
else {
// Check only for "as any"
if (isAsAny(firstArg)) {
context.report({
node: firstArg,
messageId: 'noAsAnyInT',
})
}
} }
}, },
} }

View File

@ -165,8 +165,9 @@ export default antfu(
'dify-i18n': difyI18n, 'dify-i18n': difyI18n,
}, },
rules: { rules: {
// 'dify-i18n/no-as-any-in-t': ['error', { mode: 'all' }],
'dify-i18n/no-as-any-in-t': 'error', 'dify-i18n/no-as-any-in-t': 'error',
'dify-i18n/no-legacy-namespace-prefix': 'error', // 'dify-i18n/no-legacy-namespace-prefix': 'error',
'dify-i18n/require-ns-option': 'error', 'dify-i18n/require-ns-option': 'error',
}, },
}, },

View File

@ -138,6 +138,7 @@
"semver": "^7.7.3", "semver": "^7.7.3",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"sortablejs": "^1.15.6", "sortablejs": "^1.15.6",
"string-ts": "^2.3.1",
"swr": "^2.3.6", "swr": "^2.3.6",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tldts": "^7.0.17", "tldts": "^7.0.17",

3
web/pnpm-lock.yaml generated
View File

@ -330,6 +330,9 @@ importers:
sortablejs: sortablejs:
specifier: ^1.15.6 specifier: ^1.15.6
version: 1.15.6 version: 1.15.6
string-ts:
specifier: ^2.3.1
version: 2.3.1
swr: swr:
specifier: ^2.3.6 specifier: ^2.3.6
version: 2.3.7(react@19.2.3) version: 2.3.7(react@19.2.3)