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 { Fragment } from 'react'
import { useTranslation } from 'react-i18next'
import { replace } from 'string-ts'
import AudioBtn from '@/app/components/base/audio-btn'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
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"
>
<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 className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronDownIcon
@ -119,7 +120,7 @@ const VoiceParamConfig = ({
<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"
>
{languages.map((item: Item) => (
{languages.map(item => (
<ListboxOption
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"
@ -131,7 +132,7 @@ const VoiceParamConfig = ({
<span
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>
{(selected || item.value === text2speech?.language) && (
<span

View File

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

View File

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

View File

@ -238,7 +238,10 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
list.push({
id: `${type}-need-added`,
type,
// We don't have enough type info for t() here
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' }) }),
canNavigate: false,
})

View File

@ -3,15 +3,32 @@ export default {
meta: {
type: 'problem',
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: {
noAsAnyInT:
'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) {
const options = context.options[0] || {}
const mode = options.mode || 'any'
/**
* Check if this is a t() function call
* @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 {
CallExpression(node) {
if (!isTCall(node) || node.arguments.length === 0)
@ -52,12 +86,23 @@ export default {
const firstArg = node.arguments[0]
// Check if the first argument uses "as any"
if (isAsAny(firstArg)) {
context.report({
node: firstArg,
messageId: 'noAsAnyInT',
})
if (mode === 'all') {
// Check for any type assertion
if (isAsExpression(firstArg)) {
context.report({
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,
},
rules: {
// 'dify-i18n/no-as-any-in-t': ['error', { mode: 'all' }],
'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',
},
},

View File

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

View File

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