mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 17:27:16 -05:00
chore: refactor i18n scripts and remove extra keys (#28618)
This commit is contained in:
@@ -20,22 +20,22 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check for file changes in i18n/en-US
|
||||
id: check_files
|
||||
run: |
|
||||
recent_commit_sha=$(git rev-parse HEAD)
|
||||
second_recent_commit_sha=$(git rev-parse HEAD~1)
|
||||
changed_files=$(git diff --name-only $recent_commit_sha $second_recent_commit_sha -- 'i18n/en-US/*.ts')
|
||||
git fetch origin "${{ github.event.before }}" || true
|
||||
git fetch origin "${{ github.sha }}" || true
|
||||
changed_files=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'i18n/en-US/*.ts')
|
||||
echo "Changed files: $changed_files"
|
||||
if [ -n "$changed_files" ]; then
|
||||
echo "FILES_CHANGED=true" >> $GITHUB_ENV
|
||||
file_args=""
|
||||
for file in $changed_files; do
|
||||
filename=$(basename "$file" .ts)
|
||||
file_args="$file_args --file=$filename"
|
||||
file_args="$file_args --file $filename"
|
||||
done
|
||||
echo "FILE_ARGS=$file_args" >> $GITHUB_ENV
|
||||
echo "File arguments: $file_args"
|
||||
|
||||
@@ -173,3 +173,14 @@ export const languages = [
|
||||
That's it! You have successfully added a new language to the project. If you want to remove a language, you can simply delete the folder and remove the language from the `language.ts` file.
|
||||
|
||||
We have a list of languages that we support in the `language.ts` file. But some of them are not supported yet. So, they are marked as `false`. If you want to support a language, you can follow the steps above and mark the supported field as `true`.
|
||||
|
||||
## Utility scripts
|
||||
|
||||
- Auto-fill translations: `pnpm run auto-gen-i18n -- --file app common --lang zh-Hans ja-JP [--dry-run]`
|
||||
- Use space-separated values; repeat `--file` / `--lang` as needed. Defaults to all en-US files and all supported locales except en-US.
|
||||
- Protects placeholders (`{{var}}`, `${var}`, `<tag>`) before translation and restores them after.
|
||||
- Check missing/extra keys: `pnpm run check-i18n -- --file app billing --lang zh-Hans [--auto-remove]`
|
||||
- Use space-separated values; repeat `--file` / `--lang` as needed. Returns non-zero on missing/extra keys (CI will fail); `--auto-remove` deletes extra keys automatically.
|
||||
- Generate types: `pnpm run gen:i18n-types`; verify sync: `pnpm run check:i18n-types`.
|
||||
|
||||
Workflows: `.github/workflows/translate-i18n-base-on-english.yml` auto-runs the translation generator on en-US changes to main; `.github/workflows/web-tests.yml` checks i18n keys and type sync on web changes.
|
||||
|
||||
@@ -22,83 +22,230 @@ const languageKeyMap = data.languages.reduce((map, language) => {
|
||||
return map
|
||||
}, {})
|
||||
|
||||
const supportedLanguages = Object.keys(languageKeyMap)
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
files: [],
|
||||
languages: [],
|
||||
isDryRun: false,
|
||||
help: false,
|
||||
errors: [],
|
||||
}
|
||||
|
||||
const collectValues = (startIndex) => {
|
||||
const values = []
|
||||
let cursor = startIndex + 1
|
||||
while (cursor < argv.length && !argv[cursor].startsWith('--')) {
|
||||
const value = argv[cursor].trim()
|
||||
if (value) values.push(value)
|
||||
cursor++
|
||||
}
|
||||
return { values, nextIndex: cursor - 1 }
|
||||
}
|
||||
|
||||
const validateList = (values, flag) => {
|
||||
if (!values.length) {
|
||||
args.errors.push(`${flag} requires at least one value. Example: ${flag} app billing`)
|
||||
return false
|
||||
}
|
||||
|
||||
const invalid = values.find(value => value.includes(','))
|
||||
if (invalid) {
|
||||
args.errors.push(`${flag} expects space-separated values. Example: ${flag} app billing`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
for (let index = 2; index < argv.length; index++) {
|
||||
const arg = argv[index]
|
||||
|
||||
if (arg === '--dry-run') {
|
||||
args.isDryRun = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg === '--help' || arg === '-h') {
|
||||
args.help = true
|
||||
break
|
||||
}
|
||||
|
||||
if (arg.startsWith('--file=')) {
|
||||
args.errors.push('--file expects space-separated values. Example: --file app billing')
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg === '--file') {
|
||||
const { values, nextIndex } = collectValues(index)
|
||||
if (validateList(values, '--file'))
|
||||
args.files.push(...values)
|
||||
index = nextIndex
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg.startsWith('--lang=')) {
|
||||
args.errors.push('--lang expects space-separated values. Example: --lang zh-Hans ja-JP')
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg === '--lang') {
|
||||
const { values, nextIndex } = collectValues(index)
|
||||
if (validateList(values, '--lang'))
|
||||
args.languages.push(...values)
|
||||
index = nextIndex
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`Usage: pnpm run auto-gen-i18n [options]
|
||||
|
||||
Options:
|
||||
--file <name...> Process only specific files; provide space-separated names and repeat --file if needed
|
||||
--lang <locale> Process only specific locales; provide space-separated locales and repeat --lang if needed (default: all supported except en-US)
|
||||
--dry-run Preview changes without writing files
|
||||
-h, --help Show help
|
||||
|
||||
Examples:
|
||||
pnpm run auto-gen-i18n -- --file app common --lang zh-Hans ja-JP
|
||||
pnpm run auto-gen-i18n -- --dry-run
|
||||
`)
|
||||
}
|
||||
|
||||
function protectPlaceholders(text) {
|
||||
const placeholders = []
|
||||
let safeText = text
|
||||
const patterns = [
|
||||
/\{\{[^{}]+\}\}/g, // mustache
|
||||
/\$\{[^{}]+\}/g, // template expressions
|
||||
/<[^>]+?>/g, // html-like tags
|
||||
]
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
safeText = safeText.replace(pattern, (match) => {
|
||||
const token = `__PH_${placeholders.length}__`
|
||||
placeholders.push({ token, value: match })
|
||||
return token
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
safeText,
|
||||
restore(translated) {
|
||||
return placeholders.reduce((result, { token, value }) => result.replace(new RegExp(token, 'g'), value), translated)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function translateText(source, toLanguage) {
|
||||
if (typeof source !== 'string')
|
||||
return { value: source, skipped: false }
|
||||
|
||||
const trimmed = source.trim()
|
||||
if (!trimmed)
|
||||
return { value: source, skipped: false }
|
||||
|
||||
const { safeText, restore } = protectPlaceholders(source)
|
||||
|
||||
try {
|
||||
const { translation } = await translate(safeText, null, languageKeyMap[toLanguage])
|
||||
return { value: restore(translation), skipped: false }
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`❌ Error translating to ${toLanguage}:`, error.message)
|
||||
return { value: source, skipped: true, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
async function translateMissingKeyDeeply(sourceObj, targetObject, toLanguage) {
|
||||
const skippedKeys = []
|
||||
const translatedKeys = []
|
||||
|
||||
await Promise.all(Object.keys(sourceObj).map(async (key) => {
|
||||
if (targetObject[key] === undefined) {
|
||||
if (typeof sourceObj[key] === 'object') {
|
||||
targetObject[key] = {}
|
||||
const result = await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
skippedKeys.push(...result.skipped)
|
||||
translatedKeys.push(...result.translated)
|
||||
const entries = Object.keys(sourceObj)
|
||||
|
||||
const processArray = async (sourceArray, targetArray, parentKey) => {
|
||||
for (let i = 0; i < sourceArray.length; i++) {
|
||||
const item = sourceArray[i]
|
||||
const pathKey = `${parentKey}[${i}]`
|
||||
|
||||
const existingTarget = targetArray[i]
|
||||
|
||||
if (typeof item === 'object' && item !== null) {
|
||||
const targetChild = (Array.isArray(existingTarget) || typeof existingTarget === 'object') ? existingTarget : (Array.isArray(item) ? [] : {})
|
||||
const childResult = await translateMissingKeyDeeply(item, targetChild, toLanguage)
|
||||
targetArray[i] = targetChild
|
||||
skippedKeys.push(...childResult.skipped.map(k => `${pathKey}.${k}`))
|
||||
translatedKeys.push(...childResult.translated.map(k => `${pathKey}.${k}`))
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const source = sourceObj[key]
|
||||
if (!source) {
|
||||
targetObject[key] = ''
|
||||
return
|
||||
}
|
||||
if (existingTarget !== undefined)
|
||||
continue
|
||||
|
||||
// Skip template literal placeholders
|
||||
if (source === 'TEMPLATE_LITERAL_PLACEHOLDER') {
|
||||
console.log(`⏭️ Skipping template literal key: "${key}"`)
|
||||
skippedKeys.push(`${key}: ${source}`)
|
||||
return
|
||||
}
|
||||
|
||||
// Only skip obvious code patterns, not normal text with parentheses
|
||||
const codePatterns = [
|
||||
/\{\{.*\}\}/, // Template variables like {{key}}
|
||||
/\$\{.*\}/, // Template literals ${...}
|
||||
/<[^>]+>/, // HTML/XML tags
|
||||
/function\s*\(/, // Function definitions
|
||||
/=\s*\(/, // Assignment with function calls
|
||||
]
|
||||
|
||||
const isCodeLike = codePatterns.some(pattern => pattern.test(source))
|
||||
if (isCodeLike) {
|
||||
console.log(`⏭️ Skipping code-like content: "${source.substring(0, 50)}..."`)
|
||||
skippedKeys.push(`${key}: ${source}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`🔄 Translating: "${source}" to ${toLanguage}`)
|
||||
const { translation } = await translate(sourceObj[key], null, languageKeyMap[toLanguage])
|
||||
targetObject[key] = translation
|
||||
translatedKeys.push(`${key}: ${translation}`)
|
||||
console.log(`✅ Translated: "${translation}"`)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`❌ Error translating "${sourceObj[key]}" to ${toLanguage}. Key: ${key}`, error.message)
|
||||
skippedKeys.push(`${key}: ${sourceObj[key]} (Error: ${error.message})`)
|
||||
|
||||
// Add retry mechanism for network errors
|
||||
if (error.message.includes('network') || error.message.includes('timeout')) {
|
||||
console.log(`🔄 Retrying translation for key: ${key}`)
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // Wait 1 second
|
||||
const { translation } = await translate(sourceObj[key], null, languageKeyMap[toLanguage])
|
||||
targetObject[key] = translation
|
||||
translatedKeys.push(`${key}: ${translation}`)
|
||||
console.log(`✅ Retry successful: "${translation}"`)
|
||||
}
|
||||
catch (retryError) {
|
||||
console.error(`❌ Retry failed for key ${key}:`, retryError.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
const translationResult = await translateText(item, toLanguage)
|
||||
targetArray[i] = translationResult.value ?? ''
|
||||
if (translationResult.skipped)
|
||||
skippedKeys.push(`${pathKey}: ${item}`)
|
||||
else
|
||||
translatedKeys.push(pathKey)
|
||||
}
|
||||
}
|
||||
else if (typeof sourceObj[key] === 'object') {
|
||||
targetObject[key] = targetObject[key] || {}
|
||||
const result = await translateMissingKeyDeeply(sourceObj[key], targetObject[key], toLanguage)
|
||||
skippedKeys.push(...result.skipped)
|
||||
translatedKeys.push(...result.translated)
|
||||
}
|
||||
|
||||
for (const key of entries) {
|
||||
const sourceValue = sourceObj[key]
|
||||
const targetValue = targetObject[key]
|
||||
|
||||
if (targetValue === undefined) {
|
||||
if (Array.isArray(sourceValue)) {
|
||||
const translatedArray = []
|
||||
await processArray(sourceValue, translatedArray, key)
|
||||
targetObject[key] = translatedArray
|
||||
}
|
||||
else if (typeof sourceValue === 'object' && sourceValue !== null) {
|
||||
targetObject[key] = {}
|
||||
const result = await translateMissingKeyDeeply(sourceValue, targetObject[key], toLanguage)
|
||||
skippedKeys.push(...result.skipped.map(k => `${key}.${k}`))
|
||||
translatedKeys.push(...result.translated.map(k => `${key}.${k}`))
|
||||
}
|
||||
else {
|
||||
const translationResult = await translateText(sourceValue, toLanguage)
|
||||
targetObject[key] = translationResult.value ?? ''
|
||||
if (translationResult.skipped)
|
||||
skippedKeys.push(`${key}: ${sourceValue}`)
|
||||
else
|
||||
translatedKeys.push(key)
|
||||
}
|
||||
}
|
||||
}))
|
||||
else if (Array.isArray(sourceValue)) {
|
||||
const targetArray = Array.isArray(targetValue) ? targetValue : []
|
||||
await processArray(sourceValue, targetArray, key)
|
||||
targetObject[key] = targetArray
|
||||
}
|
||||
else if (typeof sourceValue === 'object' && sourceValue !== null) {
|
||||
const targetChild = targetValue && typeof targetValue === 'object' ? targetValue : {}
|
||||
targetObject[key] = targetChild
|
||||
const result = await translateMissingKeyDeeply(sourceValue, targetChild, toLanguage)
|
||||
skippedKeys.push(...result.skipped.map(k => `${key}.${k}`))
|
||||
translatedKeys.push(...result.translated.map(k => `${key}.${k}`))
|
||||
}
|
||||
else {
|
||||
// Overwrite when type is different or value is missing to keep structure in sync
|
||||
const shouldUpdate = typeof targetValue !== typeof sourceValue || targetValue === undefined || targetValue === null
|
||||
if (shouldUpdate) {
|
||||
const translationResult = await translateText(sourceValue, toLanguage)
|
||||
targetObject[key] = translationResult.value ?? ''
|
||||
if (translationResult.skipped)
|
||||
skippedKeys.push(`${key}: ${sourceValue}`)
|
||||
else
|
||||
translatedKeys.push(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { skipped: skippedKeys, translated: translatedKeys }
|
||||
}
|
||||
@@ -109,15 +256,6 @@ async function autoGenTrans(fileName, toGenLanguage, isDryRun = false) {
|
||||
try {
|
||||
const content = fs.readFileSync(fullKeyFilePath, 'utf8')
|
||||
|
||||
// Temporarily replace template literals with regular strings for AST parsing
|
||||
// This allows us to process other keys while skipping problematic ones
|
||||
let processedContent = content
|
||||
const templateLiteralPattern = /(resolutionTooltip):\s*`([^`]*)`/g
|
||||
processedContent = processedContent.replace(templateLiteralPattern, (match, key, value) => {
|
||||
console.log(`⏭️ Temporarily replacing template literal for key: ${key}`)
|
||||
return `${key}: "TEMPLATE_LITERAL_PLACEHOLDER"`
|
||||
})
|
||||
|
||||
// Create a safer module environment for vm
|
||||
const moduleExports = {}
|
||||
const context = {
|
||||
@@ -130,7 +268,7 @@ async function autoGenTrans(fileName, toGenLanguage, isDryRun = false) {
|
||||
}
|
||||
|
||||
// Use vm.runInNewContext instead of eval for better security
|
||||
vm.runInNewContext(transpile(processedContent), context)
|
||||
vm.runInNewContext(transpile(content), context)
|
||||
|
||||
const fullKeyContent = moduleExports.default || moduleExports
|
||||
|
||||
@@ -149,13 +287,7 @@ export default translation
|
||||
const readContent = await loadFile(toGenLanguageFilePath)
|
||||
const { code: toGenContent } = generateCode(readContent)
|
||||
|
||||
// Also handle template literals in target file content
|
||||
let processedToGenContent = toGenContent
|
||||
processedToGenContent = processedToGenContent.replace(templateLiteralPattern, (match, key, value) => {
|
||||
console.log(`⏭️ Temporarily replacing template literal in target file for key: ${key}`)
|
||||
return `${key}: "TEMPLATE_LITERAL_PLACEHOLDER"`
|
||||
})
|
||||
const mod = await parseModule(`export default ${processedToGenContent.replace('export default translation', '').replace('const translation = ', '')}`)
|
||||
const mod = await parseModule(`export default ${toGenContent.replace('export default translation', '').replace('const translation = ', '')}`)
|
||||
const toGenOutPut = mod.exports.default
|
||||
|
||||
console.log(`\n🌍 Processing ${fileName} for ${toGenLanguage}...`)
|
||||
@@ -179,21 +311,6 @@ export default translation
|
||||
export default translation
|
||||
`.replace(/,\n\n/g, ',\n').replace('};', '}')
|
||||
|
||||
// Restore original template literals by reading from the original target file if it exists
|
||||
if (fs.existsSync(toGenLanguageFilePath)) {
|
||||
const originalContent = fs.readFileSync(toGenLanguageFilePath, 'utf8')
|
||||
// Extract original template literal content for resolutionTooltip
|
||||
const originalMatch = originalContent.match(/(resolutionTooltip):\s*`([^`]*)`/s)
|
||||
if (originalMatch) {
|
||||
const [fullMatch, key, value] = originalMatch
|
||||
res = res.replace(
|
||||
`${key}: "TEMPLATE_LITERAL_PLACEHOLDER"`,
|
||||
`${key}: \`${value}\``,
|
||||
)
|
||||
console.log(`🔄 Restored original template literal for key: ${key}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDryRun) {
|
||||
fs.writeFileSync(toGenLanguageFilePath, res)
|
||||
console.log(`💾 Saved translations to ${toGenLanguageFilePath}`)
|
||||
@@ -211,11 +328,10 @@ export default translation
|
||||
}
|
||||
|
||||
// Add command line argument support
|
||||
const isDryRun = process.argv.includes('--dry-run')
|
||||
const targetFiles = process.argv
|
||||
.filter(arg => arg.startsWith('--file='))
|
||||
.map(arg => arg.split('=')[1])
|
||||
const targetLang = process.argv.find(arg => arg.startsWith('--lang='))?.split('=')[1]
|
||||
const args = parseArgs(process.argv)
|
||||
const isDryRun = args.isDryRun
|
||||
const targetFiles = args.files
|
||||
const targetLangs = args.languages
|
||||
|
||||
// Rate limiting helper
|
||||
function delay(ms) {
|
||||
@@ -223,18 +339,46 @@ function delay(ms) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (args.help) {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
if (args.errors.length) {
|
||||
args.errors.forEach(message => console.error(`❌ ${message}`))
|
||||
printHelp()
|
||||
process.exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('🚀 Starting auto-gen-i18n script...')
|
||||
console.log(`📋 Mode: ${isDryRun ? 'DRY RUN (no files will be modified)' : 'LIVE MODE'}`)
|
||||
|
||||
const files = fs
|
||||
const filesInEn = fs
|
||||
.readdirSync(path.resolve(__dirname, i18nFolder, targetLanguage))
|
||||
.filter(file => /\.ts$/.test(file)) // Only process .ts files
|
||||
.map(file => file.replace(/\.ts$/, ''))
|
||||
// Removed app-debug exclusion, now only skip specific problematic keys
|
||||
|
||||
// Filter by target files if specified
|
||||
const filesToProcess = targetFiles.length > 0 ? files.filter(f => targetFiles.includes(f)) : files
|
||||
const languagesToProcess = targetLang ? [targetLang] : Object.keys(languageKeyMap)
|
||||
const filesToProcess = targetFiles.length > 0 ? filesInEn.filter(f => targetFiles.includes(f)) : filesInEn
|
||||
const languagesToProcess = Array.from(new Set((targetLangs.length > 0 ? targetLangs : supportedLanguages)
|
||||
.filter(lang => lang !== targetLanguage)))
|
||||
|
||||
const unknownLangs = languagesToProcess.filter(lang => !languageKeyMap[lang])
|
||||
if (unknownLangs.length) {
|
||||
console.error(`❌ Unsupported languages: ${unknownLangs.join(', ')}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!filesToProcess.length) {
|
||||
console.log('ℹ️ No files to process based on provided arguments')
|
||||
return
|
||||
}
|
||||
|
||||
if (!languagesToProcess.length) {
|
||||
console.log('ℹ️ No languages to process (did you only specify en-US?)')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`📁 Files to process: ${filesToProcess.join(', ')}`)
|
||||
console.log(`🌍 Languages to process: ${languagesToProcess.join(', ')}`)
|
||||
@@ -273,6 +417,12 @@ async function main() {
|
||||
|
||||
if (isDryRun)
|
||||
console.log('\n💡 This was a dry run. To actually translate, run without --dry-run flag.')
|
||||
|
||||
if (totalErrors > 0)
|
||||
process.exitCode = 1
|
||||
}
|
||||
|
||||
main()
|
||||
main().catch((error) => {
|
||||
console.error('❌ Unexpected error:', error.message)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
@@ -3,26 +3,36 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { camelCase } = require('lodash')
|
||||
const ts = require('typescript')
|
||||
|
||||
// Import the NAMESPACES array from i18next-config.ts
|
||||
function getNamespacesFromConfig() {
|
||||
const configPath = path.join(__dirname, 'i18next-config.ts')
|
||||
const configContent = fs.readFileSync(configPath, 'utf8')
|
||||
|
||||
// Extract NAMESPACES array using regex
|
||||
const namespacesMatch = configContent.match(/const NAMESPACES = \[([\s\S]*?)\]/)
|
||||
if (!namespacesMatch) {
|
||||
throw new Error('Could not find NAMESPACES array in i18next-config.ts')
|
||||
const sourceFile = ts.createSourceFile(configPath, configContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
||||
|
||||
const namespaces = []
|
||||
|
||||
const visit = (node) => {
|
||||
if (
|
||||
ts.isVariableDeclaration(node)
|
||||
&& node.name.getText() === 'NAMESPACES'
|
||||
&& node.initializer
|
||||
&& ts.isArrayLiteralExpression(node.initializer)
|
||||
) {
|
||||
node.initializer.elements.forEach((el) => {
|
||||
if (ts.isStringLiteral(el))
|
||||
namespaces.push(el.text)
|
||||
})
|
||||
}
|
||||
ts.forEachChild(node, visit)
|
||||
}
|
||||
|
||||
// Parse the namespaces
|
||||
const namespacesStr = namespacesMatch[1]
|
||||
const namespaces = namespacesStr
|
||||
.split(',')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.startsWith("'") || line.startsWith('"'))
|
||||
.map(line => line.slice(1, -1)) // Remove quotes
|
||||
|
||||
|
||||
visit(sourceFile)
|
||||
|
||||
if (!namespaces.length)
|
||||
throw new Error('Could not find NAMESPACES array in i18next-config.ts')
|
||||
|
||||
return namespaces
|
||||
}
|
||||
|
||||
@@ -117,4 +127,4 @@ function main() {
|
||||
|
||||
if (require.main === module) {
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,99 @@ const targetLanguage = 'en-US'
|
||||
const data = require('./languages.json')
|
||||
const languages = data.languages.filter(language => language.supported).map(language => language.value)
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
files: [],
|
||||
languages: [],
|
||||
autoRemove: false,
|
||||
help: false,
|
||||
errors: [],
|
||||
}
|
||||
|
||||
const collectValues = (startIndex) => {
|
||||
const values = []
|
||||
let cursor = startIndex + 1
|
||||
while (cursor < argv.length && !argv[cursor].startsWith('--')) {
|
||||
const value = argv[cursor].trim()
|
||||
if (value) values.push(value)
|
||||
cursor++
|
||||
}
|
||||
return { values, nextIndex: cursor - 1 }
|
||||
}
|
||||
|
||||
const validateList = (values, flag) => {
|
||||
if (!values.length) {
|
||||
args.errors.push(`${flag} requires at least one value. Example: ${flag} app billing`)
|
||||
return false
|
||||
}
|
||||
|
||||
const invalid = values.find(value => value.includes(','))
|
||||
if (invalid) {
|
||||
args.errors.push(`${flag} expects space-separated values. Example: ${flag} app billing`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
for (let index = 2; index < argv.length; index++) {
|
||||
const arg = argv[index]
|
||||
|
||||
if (arg === '--auto-remove') {
|
||||
args.autoRemove = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg === '--help' || arg === '-h') {
|
||||
args.help = true
|
||||
break
|
||||
}
|
||||
|
||||
if (arg.startsWith('--file=')) {
|
||||
args.errors.push('--file expects space-separated values. Example: --file app billing')
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg === '--file') {
|
||||
const { values, nextIndex } = collectValues(index)
|
||||
if (validateList(values, '--file'))
|
||||
args.files.push(...values)
|
||||
index = nextIndex
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg.startsWith('--lang=')) {
|
||||
args.errors.push('--lang expects space-separated values. Example: --lang zh-Hans ja-JP')
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg === '--lang') {
|
||||
const { values, nextIndex } = collectValues(index)
|
||||
if (validateList(values, '--lang'))
|
||||
args.languages.push(...values)
|
||||
index = nextIndex
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`Usage: pnpm run check-i18n [options]
|
||||
|
||||
Options:
|
||||
--file <name...> Check only specific files; provide space-separated names and repeat --file if needed
|
||||
--lang <locale> Check only specific locales; provide space-separated locales and repeat --lang if needed
|
||||
--auto-remove Remove extra keys automatically
|
||||
-h, --help Show help
|
||||
|
||||
Examples:
|
||||
pnpm run check-i18n -- --file app billing --lang zh-Hans ja-JP
|
||||
pnpm run check-i18n -- --auto-remove
|
||||
`)
|
||||
}
|
||||
|
||||
async function getKeysFromLanguage(language) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const folderPath = path.resolve(__dirname, '../i18n', language)
|
||||
@@ -273,27 +366,30 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) {
|
||||
}
|
||||
|
||||
// Add command line argument support
|
||||
const targetFile = process.argv.find(arg => arg.startsWith('--file='))?.split('=')[1]
|
||||
const targetLang = process.argv.find(arg => arg.startsWith('--lang='))?.split('=')[1]
|
||||
const autoRemove = process.argv.includes('--auto-remove')
|
||||
const args = parseArgs(process.argv)
|
||||
const targetFiles = Array.from(new Set(args.files))
|
||||
const targetLangs = Array.from(new Set(args.languages))
|
||||
const autoRemove = args.autoRemove
|
||||
|
||||
async function main() {
|
||||
const compareKeysCount = async () => {
|
||||
let hasDiff = false
|
||||
const allTargetKeys = await getKeysFromLanguage(targetLanguage)
|
||||
|
||||
// Filter target keys by file if specified
|
||||
const targetKeys = targetFile
|
||||
? allTargetKeys.filter(key => key.startsWith(`${targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())}.`))
|
||||
const camelTargetFiles = targetFiles.map(file => file.replace(/[-_](.)/g, (_, c) => c.toUpperCase()))
|
||||
const targetKeys = targetFiles.length
|
||||
? allTargetKeys.filter(key => camelTargetFiles.some(file => key.startsWith(`${file}.`)))
|
||||
: allTargetKeys
|
||||
|
||||
// Filter languages by target language if specified
|
||||
const languagesToProcess = targetLang ? [targetLang] : languages
|
||||
const languagesToProcess = targetLangs.length ? targetLangs : languages
|
||||
|
||||
const allLanguagesKeys = await Promise.all(languagesToProcess.map(language => getKeysFromLanguage(language)))
|
||||
|
||||
// Filter language keys by file if specified
|
||||
const languagesKeys = targetFile
|
||||
? allLanguagesKeys.map(keys => keys.filter(key => key.startsWith(`${targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())}.`)))
|
||||
const languagesKeys = targetFiles.length
|
||||
? allLanguagesKeys.map(keys => keys.filter(key => camelTargetFiles.some(file => key.startsWith(`${file}.`))))
|
||||
: allLanguagesKeys
|
||||
|
||||
const keysCount = languagesKeys.map(keys => keys.length)
|
||||
@@ -316,6 +412,8 @@ async function main() {
|
||||
const extraKeys = languageKeys.filter(key => !targetKeys.includes(key))
|
||||
|
||||
console.log(`Missing keys in ${language}:`, missingKeys)
|
||||
if (missingKeys.length > 0)
|
||||
hasDiff = true
|
||||
|
||||
// Show extra keys only when there are extra keys (negative difference)
|
||||
if (extraKeys.length > 0) {
|
||||
@@ -330,7 +428,7 @@ async function main() {
|
||||
const files = fs.readdirSync(i18nFolder)
|
||||
.filter(file => /\.ts$/.test(file))
|
||||
.map(file => file.replace(/\.ts$/, ''))
|
||||
.filter(f => !targetFile || f === targetFile) // Filter by target file if specified
|
||||
.filter(f => targetFiles.length === 0 || targetFiles.includes(f))
|
||||
|
||||
let totalRemoved = 0
|
||||
for (const fileName of files) {
|
||||
@@ -340,21 +438,59 @@ async function main() {
|
||||
|
||||
console.log(`✅ Auto-removal completed for ${language}. Modified ${totalRemoved} files.`)
|
||||
}
|
||||
else {
|
||||
hasDiff = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasDiff
|
||||
}
|
||||
|
||||
console.log('🚀 Starting check-i18n script...')
|
||||
if (targetFile)
|
||||
console.log(`📁 Checking file: ${targetFile}`)
|
||||
if (targetFiles.length)
|
||||
console.log(`📁 Checking files: ${targetFiles.join(', ')}`)
|
||||
|
||||
if (targetLang)
|
||||
console.log(`🌍 Checking language: ${targetLang}`)
|
||||
if (targetLangs.length)
|
||||
console.log(`🌍 Checking languages: ${targetLangs.join(', ')}`)
|
||||
|
||||
if (autoRemove)
|
||||
console.log('🤖 Auto-remove mode: ENABLED')
|
||||
|
||||
compareKeysCount()
|
||||
const hasDiff = await compareKeysCount()
|
||||
if (hasDiff) {
|
||||
console.error('\n❌ i18n keys are not aligned. Fix issues above.')
|
||||
process.exitCode = 1
|
||||
}
|
||||
else {
|
||||
console.log('\n✅ All i18n files are in sync')
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
async function bootstrap() {
|
||||
if (args.help) {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
if (args.errors.length) {
|
||||
args.errors.forEach(message => console.error(`❌ ${message}`))
|
||||
printHelp()
|
||||
process.exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
const unknownLangs = targetLangs.filter(lang => !languages.includes(lang))
|
||||
if (unknownLangs.length) {
|
||||
console.error(`❌ Unsupported languages: ${unknownLangs.join(', ')}`)
|
||||
process.exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
await main()
|
||||
}
|
||||
|
||||
bootstrap().catch((error) => {
|
||||
console.error('❌ Unexpected error:', error.message)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
@@ -3,26 +3,36 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { camelCase } = require('lodash')
|
||||
const ts = require('typescript')
|
||||
|
||||
// Import the NAMESPACES array from i18next-config.ts
|
||||
function getNamespacesFromConfig() {
|
||||
const configPath = path.join(__dirname, 'i18next-config.ts')
|
||||
const configContent = fs.readFileSync(configPath, 'utf8')
|
||||
|
||||
// Extract NAMESPACES array using regex
|
||||
const namespacesMatch = configContent.match(/const NAMESPACES = \[([\s\S]*?)\]/)
|
||||
if (!namespacesMatch) {
|
||||
throw new Error('Could not find NAMESPACES array in i18next-config.ts')
|
||||
const sourceFile = ts.createSourceFile(configPath, configContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
||||
|
||||
const namespaces = []
|
||||
|
||||
const visit = (node) => {
|
||||
if (
|
||||
ts.isVariableDeclaration(node)
|
||||
&& node.name.getText() === 'NAMESPACES'
|
||||
&& node.initializer
|
||||
&& ts.isArrayLiteralExpression(node.initializer)
|
||||
) {
|
||||
node.initializer.elements.forEach((el) => {
|
||||
if (ts.isStringLiteral(el))
|
||||
namespaces.push(el.text)
|
||||
})
|
||||
}
|
||||
ts.forEachChild(node, visit)
|
||||
}
|
||||
|
||||
// Parse the namespaces
|
||||
const namespacesStr = namespacesMatch[1]
|
||||
const namespaces = namespacesStr
|
||||
.split(',')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.startsWith("'") || line.startsWith('"'))
|
||||
.map(line => line.slice(1, -1)) // Remove quotes
|
||||
|
||||
|
||||
visit(sourceFile)
|
||||
|
||||
if (!namespaces.length)
|
||||
throw new Error('Could not find NAMESPACES array in i18next-config.ts')
|
||||
|
||||
return namespaces
|
||||
}
|
||||
|
||||
@@ -132,4 +142,4 @@ function main() {
|
||||
|
||||
if (require.main === module) {
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Abrechnung und Abonnements verwalten',
|
||||
buyPermissionDeniedTip: 'Bitte kontaktieren Sie Ihren Unternehmensadministrator, um zu abonnieren',
|
||||
plansCommon: {
|
||||
title: 'Wählen Sie einen Tarif, der zu Ihnen passt',
|
||||
yearlyTip: 'Erhalten Sie 2 Monate kostenlos durch jährliches Abonnieren!',
|
||||
mostPopular: 'Am beliebtesten',
|
||||
planRange: {
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'Empfehlen',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Invertierter Index',
|
||||
description: 'Ein invertierter Index ist eine Struktur, die für effiziente Abfragen verwendet wird. Organisiert nach Begriffen, zeigt jeder Begriff auf Dokumente oder Webseiten, die ihn enthalten.',
|
||||
},
|
||||
change: 'Ändern',
|
||||
changeRetrievalMethod: 'Abfragemethode ändern',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Administrar facturación y suscripciones',
|
||||
buyPermissionDeniedTip: 'Por favor, contacta al administrador de tu empresa para suscribirte',
|
||||
plansCommon: {
|
||||
title: 'Elige un plan que sea adecuado para ti',
|
||||
yearlyTip: '¡Obtén 2 meses gratis al suscribirte anualmente!',
|
||||
mostPopular: 'Más Popular',
|
||||
planRange: {
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'Recomendar',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Índice Invertido',
|
||||
description: 'El Índice Invertido es una estructura utilizada para la recuperación eficiente. Organizado por términos, cada término apunta a documentos o páginas web que lo contienen.',
|
||||
},
|
||||
change: 'Cambiar',
|
||||
changeRetrievalMethod: 'Cambiar método de recuperación',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'مدیریت صورتحسابها و اشتراکها',
|
||||
buyPermissionDeniedTip: 'لطفاً با مدیر سازمان خود تماس بگیرید تا اشتراک تهیه کنید',
|
||||
plansCommon: {
|
||||
title: 'یک طرح مناسب برای خود انتخاب کنید',
|
||||
yearlyTip: 'با اشتراک سالانه 2 ماه رایگان دریافت کنید!',
|
||||
mostPopular: 'محبوبترین',
|
||||
planRange: {
|
||||
@@ -135,15 +134,6 @@ const translation = {
|
||||
for: 'برای تیمهای بزرگ',
|
||||
priceTip: 'فقط صورتحساب سالیانه',
|
||||
features: {
|
||||
4: 'Sso',
|
||||
1: 'مجوز جواز تجاری',
|
||||
5: 'SLA های مذاکره شده توسط Dify Partners',
|
||||
2: 'ویژگی های انحصاری سازمانی',
|
||||
8: 'پشتیبانی فنی حرفه ای',
|
||||
6: 'امنیت و کنترل پیشرفته',
|
||||
7: 'به روز رسانی و نگهداری توسط Dify به طور رسمی',
|
||||
3: 'فضاهای کاری چندگانه و مدیریت سازمانی',
|
||||
0: 'راه حل های استقرار مقیاس پذیر در سطح سازمانی',
|
||||
},
|
||||
},
|
||||
community: {
|
||||
@@ -154,9 +144,6 @@ const translation = {
|
||||
name: 'جامعه',
|
||||
for: 'برای کاربران فردی، تیمهای کوچک یا پروژههای غیر تجاری',
|
||||
features: {
|
||||
1: 'فضای کاری واحد',
|
||||
2: 'با مجوز منبع باز Dify مطابقت دارد',
|
||||
0: 'تمام ویژگی های اصلی در مخزن عمومی منتشر شده است',
|
||||
},
|
||||
},
|
||||
premium: {
|
||||
@@ -169,10 +156,6 @@ const translation = {
|
||||
priceTip: 'بر اساس بازار ابری',
|
||||
comingSoon: 'پشتیبانی مایکروسافت آژور و گوگل کلود به زودی در دسترس خواهد بود',
|
||||
features: {
|
||||
1: 'فضای کاری واحد',
|
||||
3: 'پشتیبانی از ایمیل و چت اولویت دار',
|
||||
2: 'لوگوی وب اپلیکیشن و سفارشی سازی برندینگ',
|
||||
0: 'قابلیت اطمینان خود مدیریت شده توسط ارائه دهندگان مختلف ابر',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'توصیه',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'فهرست معکوس',
|
||||
description: 'فهرست معکوس یک ساختار برای بازیابی کارآمد است. توسط اصطلاحات سازماندهی شده، هر اصطلاح به اسناد یا صفحات وب حاوی آن اشاره میکند.',
|
||||
},
|
||||
change: 'تغییر',
|
||||
changeRetrievalMethod: 'تغییر روش بازیابی',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Gérer la facturation et les abonnements',
|
||||
buyPermissionDeniedTip: 'Veuillez contacter votre administrateur d\'entreprise pour vous abonner',
|
||||
plansCommon: {
|
||||
title: 'Choisissez un plan qui vous convient',
|
||||
yearlyTip: 'Obtenez 2 mois gratuitement en vous abonnant annuellement !',
|
||||
mostPopular: 'Le Plus Populaire',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Obtenez toutes les capacités et le support pour les systèmes à grande échelle et critiques pour la mission.',
|
||||
includesTitle: 'Tout ce qui est inclus dans le plan Équipe, plus :',
|
||||
features: {
|
||||
6: 'Sécurité et contrôles avancés',
|
||||
3: 'Espaces de travail multiples et gestion d’entreprise',
|
||||
4: 'SSO',
|
||||
1: 'Autorisation de licence commerciale',
|
||||
2: 'Fonctionnalités exclusives à l’entreprise',
|
||||
5: 'SLA négociés par les partenaires Dify',
|
||||
8: 'Assistance technique professionnelle',
|
||||
7: 'Mises à jour et maintenance par Dify officiellement',
|
||||
0: 'Solutions de déploiement évolutives de niveau entreprise',
|
||||
},
|
||||
for: 'Pour les équipes de grande taille',
|
||||
btnText: 'Contacter les ventes',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'Espace de travail unique',
|
||||
0: 'Toutes les fonctionnalités de base publiées dans le dépôt public',
|
||||
2: 'Conforme à la licence Open Source Dify',
|
||||
},
|
||||
name: 'Communauté',
|
||||
btnText: 'Commencez avec la communauté',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
2: 'Personnalisation du logo et de l’image de marque WebApp',
|
||||
1: 'Espace de travail unique',
|
||||
3: 'Assistance prioritaire par e-mail et chat',
|
||||
0: 'Fiabilité autogérée par différents fournisseurs de cloud',
|
||||
},
|
||||
for: 'Pour les organisations et les équipes de taille moyenne',
|
||||
includesTitle: 'Tout de la communauté, en plus :',
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'Recommander',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Index inversé',
|
||||
description: 'L\'Index inversé est une structure utilisée pour une récupération efficace. Organisé par termes, chaque terme pointe vers des documents ou des pages web le contenant.',
|
||||
},
|
||||
change: 'Changer',
|
||||
changeRetrievalMethod: 'Changer la méthode de récupération',
|
||||
|
||||
@@ -9,7 +9,6 @@ const translation = {
|
||||
buyPermissionDeniedTip:
|
||||
'सब्सक्राइब करने के लिए कृपया अपने एंटरप्राइज़ व्यवस्थापक से संपर्क करें',
|
||||
plansCommon: {
|
||||
title: 'आपके लिए सही योजना चुनें',
|
||||
yearlyTip: 'वार्षिक सब्सक्राइब करने पर 2 महीने मुफ्त पाएं!',
|
||||
mostPopular: 'सबसे लोकप्रिय',
|
||||
planRange: {
|
||||
@@ -142,15 +141,6 @@ const translation = {
|
||||
'बड़े पैमाने पर मिशन-क्रिटिकल सिस्टम के लिए पूर्ण क्षमताएं और समर्थन प्राप्त करें।',
|
||||
includesTitle: 'टीम योजना में सब कुछ, साथ में:',
|
||||
features: {
|
||||
4: 'SSO',
|
||||
6: 'उन्नत सुरक्षा और नियंत्रण',
|
||||
1: 'Commercial License Authorization',
|
||||
8: 'प्रोफेशनल तकनीकी समर्थन',
|
||||
0: 'उद्योग स्तर के बड़े पैमाने पर वितरण समाधान',
|
||||
3: 'अनेक कार्यक्षेत्र और उद्यम प्रबंधक',
|
||||
2: 'विशेष उद्यम सुविधाएँ',
|
||||
7: 'डिफी द्वारा आधिकारिक रूप से अपडेट और रखरखाव',
|
||||
5: 'डिफाई पार्टनर्स द्वारा बातचीत किए गए एसएलए',
|
||||
},
|
||||
price: 'कस्टम',
|
||||
btnText: 'बिक्री से संपर्क करें',
|
||||
@@ -159,9 +149,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'एकल कार्यक्षेत्र',
|
||||
2: 'डिफी ओपन सोर्स लाइसेंस के अनुपालन में',
|
||||
0: 'सभी मुख्य सुविधाएं सार्वजनिक संग्रह के तहत जारी की गई हैं।',
|
||||
},
|
||||
description: 'व्यक्तिगत उपयोगकर्ताओं, छोटे टीमों, या गैर-व्यावसायिक परियोजनाओं के लिए',
|
||||
for: 'व्यक्तिगत उपयोगकर्ताओं, छोटे टीमों, या गैर-व्यावसायिक परियोजनाओं के लिए',
|
||||
@@ -172,10 +159,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
1: 'एकल कार्यक्षेत्र',
|
||||
3: 'प्राथमिकता ईमेल और चैट समर्थन',
|
||||
2: 'वेब ऐप लोगो और ब्रांडिंग कस्टमाइजेशन',
|
||||
0: 'विभिन्न क्लाउड प्रदाताओं द्वारा आत्म-प्रबंधित विश्वसनीयता',
|
||||
},
|
||||
priceTip: 'क्लाउड मार्केटप्लेस के आधार पर',
|
||||
name: 'प्रीमियम',
|
||||
|
||||
@@ -41,9 +41,6 @@ const translation = {
|
||||
recommend: 'सिफारिश',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'उल्टा सूचकांक',
|
||||
description:
|
||||
'उल्टा सूचकांक एक ऐसी संरचना है जो कुशल पुनर्प्राप्ति के लिए उपयोग की जाती है। यह शब्दों द्वारा व्यवस्थित होती है, प्रत्येक शब्द उन दस्तावेज़ों या वेब पेजों की ओर इंगित करता है जिनमें वह होता है।',
|
||||
},
|
||||
change: 'बदलें',
|
||||
changeRetrievalMethod: 'पुनर्प्राप्ति विधि बदलें',
|
||||
|
||||
@@ -46,7 +46,6 @@ const translation = {
|
||||
annotatedResponse: {
|
||||
tooltip: 'Pengeditan manual dan anotasi respons memberikan kemampuan menjawab pertanyaan berkualitas tinggi yang dapat disesuaikan untuk aplikasi. (Hanya berlaku di aplikasi Chat)',
|
||||
},
|
||||
title: 'Harga yang mendukung perjalanan AI Anda',
|
||||
mostPopular: 'Populer',
|
||||
free: 'Bebas',
|
||||
freeTrialTipSuffix: 'Tidak perlu kartu kredit',
|
||||
@@ -123,9 +122,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
0: 'Semua fitur inti dirilis di bawah repositori publik',
|
||||
1: 'Ruang Kerja Tunggal',
|
||||
2: 'Sesuai dengan Lisensi Sumber Terbuka Dify',
|
||||
},
|
||||
price: 'Bebas',
|
||||
for: 'Untuk Pengguna Individu, Tim Kecil, atau Proyek Non-Komersial',
|
||||
@@ -136,10 +132,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
2: 'Kustomisasi Logo & Branding WebApp',
|
||||
3: 'Dukungan Email & Obrolan Prioritas',
|
||||
0: 'Keandalan yang dikelola sendiri oleh berbagai penyedia cloud',
|
||||
1: 'Ruang Kerja Tunggal',
|
||||
},
|
||||
name: 'Premi',
|
||||
price: 'Scalable',
|
||||
@@ -152,15 +144,6 @@ const translation = {
|
||||
},
|
||||
enterprise: {
|
||||
features: {
|
||||
5: 'SLA yang Dinegosiasikan oleh Mitra Dify',
|
||||
3: 'Beberapa Ruang Kerja & Manajemen Perusahaan',
|
||||
4: 'SSO',
|
||||
2: 'Fitur Eksklusif Enterprise',
|
||||
7: 'Pembaruan dan Pemeliharaan oleh Dify Secara Resmi',
|
||||
1: 'Otorisasi Lisensi Komersial',
|
||||
8: 'Dukungan Teknis Profesional',
|
||||
0: 'Solusi Penerapan yang Dapat Diskalakan Tingkat Perusahaan',
|
||||
6: 'Keamanan & Kontrol Tingkat Lanjut',
|
||||
},
|
||||
includesTitle: 'Semuanya mulai dari Premium, ditambah:',
|
||||
btnText: 'Hubungi Sales',
|
||||
|
||||
@@ -78,8 +78,6 @@ const translation = {
|
||||
description: 'Jalankan pencarian teks lengkap dan pencarian vektor secara bersamaan, peringkatkan ulang untuk memilih kecocokan terbaik untuk kueri pengguna. Pengguna dapat memilih untuk mengatur bobot atau mengonfigurasi ke model Rerank.',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Indeks Terbalik',
|
||||
description: 'Indeks Terbalik adalah struktur yang digunakan untuk pengambilan yang efisien. Diatur berdasarkan istilah, setiap istilah menunjuk ke dokumen atau halaman web yang berisinya.',
|
||||
},
|
||||
change: 'Ubah',
|
||||
changeRetrievalMethod: 'Ubah metode pengambilan',
|
||||
|
||||
@@ -9,7 +9,6 @@ const translation = {
|
||||
buyPermissionDeniedTip:
|
||||
'Contatta l\'amministratore della tua azienda per abbonarti',
|
||||
plansCommon: {
|
||||
title: 'Scegli un piano adatto a te',
|
||||
yearlyTip: 'Ottieni 2 mesi gratis abbonandoti annualmente!',
|
||||
mostPopular: 'Più Popolare',
|
||||
planRange: {
|
||||
@@ -142,15 +141,6 @@ const translation = {
|
||||
'Ottieni tutte le capacità e il supporto per sistemi mission-critical su larga scala.',
|
||||
includesTitle: 'Tutto nel piano Team, più:',
|
||||
features: {
|
||||
4: 'SSO',
|
||||
8: 'Supporto tecnico professionale',
|
||||
6: 'Sicurezza e controlli avanzati',
|
||||
1: 'Autorizzazione Licenza Commerciale',
|
||||
2: 'Funzionalità esclusive per le aziende',
|
||||
3: 'Spazi di lavoro multipli e gestione aziendale',
|
||||
0: 'Soluzioni di distribuzione scalabili di livello aziendale',
|
||||
5: 'SLA negoziati dai partner Dify',
|
||||
7: 'Aggiornamenti e manutenzione da parte di Dify ufficialmente',
|
||||
},
|
||||
price: 'Personalizzato',
|
||||
for: 'Per team di grandi dimensioni',
|
||||
@@ -159,9 +149,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
0: 'Tutte le funzionalità principali rilasciate nel repository pubblico',
|
||||
2: 'Conforme alla licenza Open Source Dify',
|
||||
1: 'Area di lavoro singola',
|
||||
},
|
||||
name: 'Comunità',
|
||||
btnText: 'Inizia con la comunità',
|
||||
@@ -172,10 +159,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
3: 'Supporto prioritario via e-mail e chat',
|
||||
1: 'Area di lavoro singola',
|
||||
0: 'Affidabilità autogestita da vari fornitori di servizi cloud',
|
||||
2: 'Personalizzazione del logo e del marchio WebApp',
|
||||
},
|
||||
name: 'Premium',
|
||||
priceTip: 'Basato su Cloud Marketplace',
|
||||
|
||||
@@ -41,9 +41,6 @@ const translation = {
|
||||
recommend: 'Consigliato',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Indice Invertito',
|
||||
description:
|
||||
'L\'Indice Invertito è una struttura utilizzata per il recupero efficiente. Organizzato per termini, ogni termine punta ai documenti o alle pagine web che lo contengono.',
|
||||
},
|
||||
change: 'Cambia',
|
||||
changeRetrievalMethod: 'Cambia metodo di recupero',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: '청구 및 구독 관리',
|
||||
buyPermissionDeniedTip: '구독하려면 엔터프라이즈 관리자에게 문의하세요',
|
||||
plansCommon: {
|
||||
title: '당신에게 맞는 요금제를 선택하세요',
|
||||
yearlyTip: '연간 구독 시 2개월 무료!',
|
||||
mostPopular: '가장 인기 있는',
|
||||
planRange: {
|
||||
|
||||
@@ -35,8 +35,6 @@ const translation = {
|
||||
recommend: '추천',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: '역 인덱스',
|
||||
description: '효율적인 검색에 사용되는 구조입니다. 각 용어는 문서나 웹 페이지에 포함된 것을 가리키며, 용어마다 체계적으로 정리되어 있습니다.',
|
||||
},
|
||||
change: '변경',
|
||||
changeRetrievalMethod: '검색 방법 변경',
|
||||
|
||||
@@ -9,7 +9,6 @@ const translation = {
|
||||
buyPermissionDeniedTip:
|
||||
'Skontaktuj się z administratorem swojej firmy, aby zasubskrybować',
|
||||
plansCommon: {
|
||||
title: 'Wybierz plan odpowiedni dla siebie',
|
||||
yearlyTip: 'Otrzymaj 2 miesiące za darmo, subskrybując rocznie!',
|
||||
mostPopular: 'Najpopularniejszy',
|
||||
planRange: {
|
||||
@@ -141,15 +140,6 @@ const translation = {
|
||||
'Uzyskaj pełne możliwości i wsparcie dla systemów o kluczowym znaczeniu dla misji.',
|
||||
includesTitle: 'Wszystko w planie Zespołowym, plus:',
|
||||
features: {
|
||||
4: 'Usługi rejestracji jednokrotnej',
|
||||
3: 'Wiele przestrzeni roboczych i zarządzanie przedsiębiorstwem',
|
||||
8: 'Profesjonalne wsparcie techniczne',
|
||||
7: 'Aktualizacje i konserwacja przez Dify oficjalnie',
|
||||
5: 'Umowy SLA wynegocjowane przez Dify Partners',
|
||||
0: 'Skalowalne rozwiązania wdrożeniowe klasy korporacyjnej',
|
||||
2: 'Wyjątkowe funkcje dla przedsiębiorstw',
|
||||
1: 'Autoryzacja licencji komercyjnej',
|
||||
6: 'Zaawansowane zabezpieczenia i kontrola',
|
||||
},
|
||||
priceTip: 'Tylko roczne fakturowanie',
|
||||
btnText: 'Skontaktuj się z działem sprzedaży',
|
||||
@@ -158,9 +148,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'Pojedyncza przestrzeń robocza',
|
||||
2: 'Zgodny z licencją Dify Open Source',
|
||||
0: 'Wszystkie podstawowe funkcje udostępnione w repozytorium publicznym',
|
||||
},
|
||||
includesTitle: 'Darmowe funkcje:',
|
||||
name: 'Społeczność',
|
||||
@@ -171,10 +158,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
2: 'Personalizacja logo i brandingu aplikacji internetowej',
|
||||
1: 'Pojedyncza przestrzeń robocza',
|
||||
0: 'Niezawodność samodzielnego zarządzania przez różnych dostawców usług w chmurze',
|
||||
3: 'Priorytetowa pomoc techniczna przez e-mail i czat',
|
||||
},
|
||||
description: 'Dla średnich organizacji i zespołów',
|
||||
for: 'Dla średnich organizacji i zespołów',
|
||||
|
||||
@@ -40,9 +40,6 @@ const translation = {
|
||||
recommend: 'Polecany',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Indeks odwrócony',
|
||||
description:
|
||||
'Indeks odwrócony to struktura używana do efektywnego odzyskiwania informacji. Zorganizowane według terminów, każdy termin wskazuje na dokumenty lub strony internetowe zawierające go.',
|
||||
},
|
||||
change: 'Zmień',
|
||||
changeRetrievalMethod: 'Zmień metodę odzyskiwania',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Ver informações de cobrança',
|
||||
buyPermissionDeniedTip: 'Por favor, entre em contato com o administrador da sua empresa para assinar',
|
||||
plansCommon: {
|
||||
title: 'Escolha o plano que melhor atende você',
|
||||
yearlyTip: 'Receba 2 meses grátis assinando anualmente!',
|
||||
mostPopular: 'Mais Popular',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Obtenha capacidades completas e suporte para sistemas críticos em larga escala.',
|
||||
includesTitle: 'Tudo no plano Equipe, além de:',
|
||||
features: {
|
||||
7: 'Atualizações e manutenção por Dify oficialmente',
|
||||
6: 'Segurança e controles avançados',
|
||||
0: 'Soluções de implantação escaláveis de nível empresarial',
|
||||
2: 'Recursos exclusivos da empresa',
|
||||
5: 'SLAs negociados pela Dify Partners',
|
||||
1: 'Autorização de Licença Comercial',
|
||||
8: 'Suporte Técnico Profissional',
|
||||
4: 'SSO',
|
||||
3: 'Vários espaços de trabalho e gerenciamento corporativo',
|
||||
},
|
||||
btnText: 'Contate Vendas',
|
||||
priceTip: 'Faturamento Anual Apenas',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'Espaço de trabalho individual',
|
||||
0: 'Todos os principais recursos lançados no repositório público',
|
||||
2: 'Está em conformidade com a licença de código aberto Dify',
|
||||
},
|
||||
name: 'Comunidade',
|
||||
description: 'Para Usuários Individuais, Pequenas Equipes ou Projetos Não Comerciais',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
0: 'Confiabilidade autogerenciada por vários provedores de nuvem',
|
||||
1: 'Espaço de trabalho individual',
|
||||
2: 'Personalização do logotipo e da marca do WebApp',
|
||||
3: 'Suporte prioritário por e-mail e bate-papo',
|
||||
},
|
||||
includesTitle: 'Tudo da Comunidade, além de:',
|
||||
for: 'Para organizações e equipes de médio porte',
|
||||
|
||||
@@ -35,8 +35,6 @@ const translation = {
|
||||
recommend: 'Recomendar',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Índice Invertido',
|
||||
description: 'O Índice Invertido é uma estrutura usada para recuperação eficiente. Organizado por termos, cada termo aponta para documentos ou páginas da web que o contêm.',
|
||||
},
|
||||
change: 'Alterar',
|
||||
changeRetrievalMethod: 'Alterar método de recuperação',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Gestionează facturarea și abonamentele',
|
||||
buyPermissionDeniedTip: 'Vă rugăm să contactați administratorul dvs. de întreprindere pentru a vă abona',
|
||||
plansCommon: {
|
||||
title: 'Alegeți un plan potrivit pentru dvs.',
|
||||
yearlyTip: 'Obțineți 2 luni gratuite prin abonarea anuală!',
|
||||
mostPopular: 'Cel mai popular',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Obțineți capacități și asistență complete pentru sisteme critice la scară largă.',
|
||||
includesTitle: 'Tot ce este în planul Echipă, plus:',
|
||||
features: {
|
||||
1: 'Autorizare licență comercială',
|
||||
3: 'Mai multe spații de lucru și managementul întreprinderii',
|
||||
2: 'Funcții exclusive pentru întreprinderi',
|
||||
7: 'Actualizări și întreținere de către Dify oficial',
|
||||
0: 'Soluții de implementare scalabile la nivel de întreprindere',
|
||||
4: 'SSO',
|
||||
8: 'Asistență tehnică profesională',
|
||||
5: 'SLA-uri negociate de partenerii Dify',
|
||||
6: 'Securitate și controale avansate',
|
||||
},
|
||||
for: 'Pentru echipe de mari dimensiuni',
|
||||
price: 'Personalizat',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
2: 'Respectă licența Dify Open Source',
|
||||
0: 'Toate caracteristicile de bază lansate în depozitul public',
|
||||
1: 'Spațiu de lucru unic',
|
||||
},
|
||||
description: 'Pentru utilizatori individuali, echipe mici sau proiecte necomerciale',
|
||||
btnText: 'Începe cu Comunitatea',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
1: 'Spațiu de lucru unic',
|
||||
3: 'Asistență prioritară prin e-mail și chat',
|
||||
2: 'Personalizarea logo-ului și brandingului WebApp',
|
||||
0: 'Fiabilitate autogestionată de diverși furnizori de cloud',
|
||||
},
|
||||
btnText: 'Obține Premium în',
|
||||
description: 'Pentru organizații și echipe de dimensiuni medii',
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'Recomandat',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Index Inversat',
|
||||
description: 'Indexul inversat este o structură utilizată pentru recuperare eficientă. Organizat după termeni, fiecare termen indică documentele sau paginile web care îl conțin.',
|
||||
},
|
||||
change: 'Schimbă',
|
||||
changeRetrievalMethod: 'Schimbă metoda de recuperare',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Управление счетами и подписками',
|
||||
buyPermissionDeniedTip: 'Пожалуйста, свяжитесь с администратором вашей организации, чтобы подписаться',
|
||||
plansCommon: {
|
||||
title: 'Выберите тарифный план, который подходит именно вам',
|
||||
yearlyTip: 'Получите 2 месяца бесплатно, подписавшись на год!',
|
||||
mostPopular: 'Самый популярный',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Получите полный набор возможностей и поддержку для крупномасштабных критически важных систем.',
|
||||
includesTitle: 'Все в командном плане, плюс:',
|
||||
features: {
|
||||
4: 'ССО',
|
||||
5: 'Согласованные SLA от Dify Partners',
|
||||
2: 'Эксклюзивные корпоративные функции',
|
||||
6: 'Расширенная безопасность и контроль',
|
||||
1: 'Разрешение на коммерческую лицензию',
|
||||
8: 'Профессиональная техническая поддержка',
|
||||
7: 'Обновления и обслуживание от Dify официально',
|
||||
3: 'Несколько рабочих пространств и управление предприятием',
|
||||
0: 'Масштабируемые решения для развертывания корпоративного уровня',
|
||||
},
|
||||
price: 'Пользовательский',
|
||||
priceTip: 'Только годовая подписка',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'Единое рабочее пространство',
|
||||
2: 'Соответствует лицензии Dify с открытым исходным кодом',
|
||||
0: 'Все основные функции выпущены в общедоступном репозитории',
|
||||
},
|
||||
name: 'Сообщество',
|
||||
btnText: 'Начните с сообщества',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
2: 'Настройка логотипа и брендинга WebApp',
|
||||
1: 'Единое рабочее пространство',
|
||||
3: 'Приоритетная поддержка по электронной почте и в чате',
|
||||
0: 'Самостоятельное управление надежностью от различных поставщиков облачных услуг',
|
||||
},
|
||||
description: 'Для средних организаций и команд',
|
||||
includesTitle: 'Всё из Сообщества, плюс:',
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'Рекомендуется',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Инвертированный индекс',
|
||||
description: 'Инвертированный индекс - это структура, используемая для эффективного поиска. Организованный по терминам, каждый термин указывает на документы или веб-страницы, содержащие его.',
|
||||
},
|
||||
change: 'Изменить',
|
||||
changeRetrievalMethod: 'Изменить метод поиска',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Upravljanje s plačili in naročninami',
|
||||
buyPermissionDeniedTip: 'Za naročnino kontaktirajte svojega skrbnika podjetja',
|
||||
plansCommon: {
|
||||
title: 'Izberite načrt, ki vam ustreza',
|
||||
yearlyTip: 'Z letno naročnino pridobite 2 meseca brezplačno!',
|
||||
mostPopular: 'Najbolj priljubljeno',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Pridobite vse zmogljivosti in podporo za velike sisteme kritične za misijo.',
|
||||
includesTitle: 'Vse v načrtu Ekipa, plus:',
|
||||
features: {
|
||||
4: 'SSO',
|
||||
6: 'Napredna varnost in nadzor',
|
||||
8: 'Strokovna tehnična podpora',
|
||||
2: 'Ekskluzivne funkcije za podjetja',
|
||||
1: 'Dovoljenje za komercialno licenco',
|
||||
3: 'Več delovnih prostorov in upravljanje podjetja',
|
||||
7: 'Posodobitve in vzdrževanje s strani Dify Official',
|
||||
5: 'Dogovorjene pogodbe o ravni storitev s strani Dify Partners',
|
||||
0: 'Prilagodljive rešitve za uvajanje na ravni podjetij',
|
||||
},
|
||||
priceTip: 'Letno zaračunavanje samo',
|
||||
price: 'Po meri',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
2: 'Skladen z odprtokodno licenco Dify',
|
||||
1: 'En delovni prostor',
|
||||
0: 'Vse osnovne funkcije, izdane v javnem repozitoriju',
|
||||
},
|
||||
includesTitle: 'Brezplačne funkcije:',
|
||||
price: 'Brezplačno',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
3: 'Prednostna podpora po e-pošti in klepetu',
|
||||
0: 'Samostojna zanesljivost različnih ponudnikov storitev v oblaku',
|
||||
2: 'Prilagajanje logotipa in blagovne znamke WebApp',
|
||||
1: 'En delovni prostor',
|
||||
},
|
||||
name: 'Premium',
|
||||
priceTip: 'Na podlagi oblaka Marketplace',
|
||||
|
||||
@@ -108,8 +108,6 @@ const translation = {
|
||||
recommend: 'Priporočamo',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Inverzni indeks',
|
||||
description: 'Inverzni indeks je struktura, ki se uporablja za učinkovito pridobivanje. Organizirano po izrazih, vsak izraz kaže na dokumente ali spletne strani, ki ga vsebujejo.',
|
||||
},
|
||||
change: 'Spremeni',
|
||||
changeRetrievalMethod: 'Spremeni metodo pridobivanja',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'จัดการการเรียกเก็บเงินและการสมัครใช้งาน',
|
||||
buyPermissionDeniedTip: 'โปรดติดต่อผู้ดูแลระบบองค์กรของคุณเพื่อสมัครสมาชิก',
|
||||
plansCommon: {
|
||||
title: 'เลือกแผนบริการที่เหมาะกับคุณ',
|
||||
yearlyTip: 'รับฟรี 2 เดือนโดยสมัครสมาชิกรายปี!',
|
||||
mostPopular: 'แห่ง',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'รับความสามารถและการสนับสนุนเต็มรูปแบบสําหรับระบบที่สําคัญต่อภารกิจขนาดใหญ่',
|
||||
includesTitle: 'ทุกอย่างในแผนทีม รวมถึง:',
|
||||
features: {
|
||||
4: 'SSO',
|
||||
5: 'SLA ที่เจรจาโดย Dify Partners',
|
||||
2: 'คุณสมบัติพิเศษสําหรับองค์กร',
|
||||
1: 'การอนุญาตใบอนุญาตเชิงพาณิชย์',
|
||||
8: 'การสนับสนุนด้านเทคนิคอย่างมืออาชีพ',
|
||||
6: 'การรักษาความปลอดภัยและการควบคุมขั้นสูง',
|
||||
3: 'พื้นที่ทํางานหลายแห่งและการจัดการองค์กร',
|
||||
0: 'โซลูชันการปรับใช้ที่ปรับขนาดได้ระดับองค์กร',
|
||||
7: 'การอัปเดตและบํารุงรักษาโดย Dify อย่างเป็นทางการ',
|
||||
},
|
||||
btnText: 'ติดต่อฝ่ายขาย',
|
||||
price: 'ที่กำหนดเอง',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'พื้นที่ทํางานเดียว',
|
||||
2: 'สอดคล้องกับใบอนุญาตโอเพ่นซอร์ส Dify',
|
||||
0: 'คุณสมบัติหลักทั้งหมดที่เผยแพร่ภายใต้ที่เก็บสาธารณะ',
|
||||
},
|
||||
name: 'ชุมชน',
|
||||
price: 'ฟรี',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
1: 'พื้นที่ทํางานเดียว',
|
||||
2: 'โลโก้ WebApp และการปรับแต่งแบรนด์',
|
||||
3: 'การสนับสนุนทางอีเมลและแชทลําดับความสําคัญ',
|
||||
0: 'ความน่าเชื่อถือที่จัดการด้วยตนเองโดยผู้ให้บริการคลาวด์ต่างๆ',
|
||||
},
|
||||
priceTip: 'อิงตามตลาดคลาวด์',
|
||||
for: 'สำหรับองค์กรและทีมขนาดกลาง',
|
||||
|
||||
@@ -107,8 +107,6 @@ const translation = {
|
||||
recommend: 'แนะนำ',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'ดัชนีกลับด้าน',
|
||||
description: 'Inverted Index เป็นโครงสร้างที่ใช้สําหรับการดึงข้อมูลอย่างมีประสิทธิภาพ จัดเรียงตามคําศัพท์ แต่ละคําชี้ไปที่เอกสารหรือหน้าเว็บที่มีคําดังกล่าว',
|
||||
},
|
||||
change: 'เปลี่ยน',
|
||||
changeRetrievalMethod: 'วิธีการเรียกดูการเปลี่ยนแปลง',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Faturalandırma ve abonelikleri yönet',
|
||||
buyPermissionDeniedTip: 'Abone olmak için lütfen işletme yöneticinize başvurun',
|
||||
plansCommon: {
|
||||
title: 'Size uygun bir plan seçin',
|
||||
yearlyTip: 'Yıllık abonelikle 2 ay ücretsiz!',
|
||||
mostPopular: 'En Popüler',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Büyük ölçekli kritik sistemler için tam yetenekler ve destek.',
|
||||
includesTitle: 'Takım plandaki her şey, artı:',
|
||||
features: {
|
||||
4: 'SSO',
|
||||
2: 'Özel Kurumsal Özellikler',
|
||||
1: 'Ticari Lisans Yetkilendirmesi',
|
||||
3: 'Çoklu Çalışma Alanları ve Kurumsal Yönetim',
|
||||
8: 'Profesyonel Teknik Destek',
|
||||
6: 'Gelişmiş Güvenlik ve Kontroller',
|
||||
7: 'Resmi olarak Dify tarafından Güncellemeler ve Bakım',
|
||||
0: 'Kurumsal Düzeyde Ölçeklenebilir Dağıtım Çözümleri',
|
||||
5: 'Dify Partners tarafından müzakere edilen SLA\'lar',
|
||||
},
|
||||
priceTip: 'Yıllık Faturalama Sadece',
|
||||
for: 'Büyük boyutlu Takımlar için',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'Tek Çalışma Alanı',
|
||||
2: 'Dify Açık Kaynak Lisansı ile uyumludur',
|
||||
0: 'Genel depo altında yayınlanan tüm temel özellikler',
|
||||
},
|
||||
price: 'Ücretsiz',
|
||||
includesTitle: 'Ücretsiz Özellikler:',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
1: 'Tek Çalışma Alanı',
|
||||
3: 'Öncelikli E-posta ve Sohbet Desteği',
|
||||
2: 'WebApp Logosu ve Marka Özelleştirmesi',
|
||||
0: 'Çeşitli Bulut Sağlayıcıları Tarafından Kendi Kendini Yöneten Güvenilirlik',
|
||||
},
|
||||
name: 'Premium',
|
||||
includesTitle: 'Topluluktan her şey, artı:',
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'Önerilir',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Ters Dizine Kayıt',
|
||||
description: 'Ters Dizine Kayıt, verimli geri alım için kullanılan bir yapıdır. Terimlere göre düzenlenir ve her terim, onu içeren belgelere veya web sayfalarına işaret eder.',
|
||||
},
|
||||
change: 'Değiştir',
|
||||
changeRetrievalMethod: 'Geri alma yöntemini değiştir',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Керувати рахунками та підписками',
|
||||
buyPermissionDeniedTip: 'Зв\'яжіться з адміністратором вашого підприємства, щоб оформити підписку',
|
||||
plansCommon: {
|
||||
title: 'Виберіть план, який підходить саме вам',
|
||||
yearlyTip: 'Отримайте 2 місяці безкоштовно, оформивши річну підписку!',
|
||||
mostPopular: 'Найпопулярніший',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Отримайте повні можливості та підтримку для масштабних критично важливих систем.',
|
||||
includesTitle: 'Все, що входить до плану Team, плюс:',
|
||||
features: {
|
||||
4: 'Єдиний вхід',
|
||||
8: 'Професійна технічна підтримка',
|
||||
1: 'Авторизація комерційної ліцензії',
|
||||
2: 'Ексклюзивні функції підприємства',
|
||||
6: 'Розширені функції безпеки та керування',
|
||||
5: 'Угода про рівень обслуговування за домовленістю від Dify Partners',
|
||||
3: 'Кілька робочих областей і управління підприємством',
|
||||
0: 'Масштабовані рішення для розгортання корпоративного рівня',
|
||||
7: 'Оновлення та обслуговування від Dify Official',
|
||||
},
|
||||
btnText: 'Зв\'язатися з відділом продажу',
|
||||
priceTip: 'Тільки річна оплата',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: 'Єдине робоче місце',
|
||||
2: 'Відповідає ліцензії Dify з відкритим вихідним кодом',
|
||||
0: 'Усі основні функції випущено в загальнодоступному репозиторії',
|
||||
},
|
||||
btnText: 'Розпочніть з громади',
|
||||
includesTitle: 'Безкоштовні можливості:',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
1: 'Єдине робоче місце',
|
||||
2: 'Налаштування логотипу WebApp та брендингу',
|
||||
0: 'Самокерована надійність різними хмарними провайдерами',
|
||||
3: 'Пріоритетна підтримка електронною поштою та в чаті',
|
||||
},
|
||||
description: 'Для середніх підприємств та команд',
|
||||
btnText: 'Отримайте Преміум у',
|
||||
|
||||
@@ -37,8 +37,6 @@ const translation = {
|
||||
recommend: 'Рекомендовано',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Інвертований індекс',
|
||||
description: 'Інвертований індекс – це структура, яка використовується для ефективного пошуку. Організований за термінами, кожен термін вказує на документи або веб-сторінки, що його містять.',
|
||||
},
|
||||
change: 'Змінити',
|
||||
changeRetrievalMethod: 'Змінити метод пошуку',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: 'Quản lý thanh toán và đăng ký',
|
||||
buyPermissionDeniedTip: 'Vui lòng liên hệ với quản trị viên doanh nghiệp của bạn để đăng ký',
|
||||
plansCommon: {
|
||||
title: 'Chọn một kế hoạch phù hợp với bạn',
|
||||
yearlyTip: 'Nhận 2 tháng miễn phí khi đăng ký hàng năm!',
|
||||
mostPopular: 'Phổ biến nhất',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: 'Nhận toàn bộ khả năng và hỗ trợ cho các hệ thống quan trọng cho nhiệm vụ quy mô lớn.',
|
||||
includesTitle: 'Tất cả trong kế hoạch Nhóm, cộng thêm:',
|
||||
features: {
|
||||
4: 'SSO',
|
||||
8: 'Hỗ trợ kỹ thuật chuyên nghiệp',
|
||||
1: 'Ủy quyền giấy phép thương mại',
|
||||
6: 'Bảo mật & Kiểm soát nâng cao',
|
||||
3: 'Nhiều không gian làm việc & quản lý doanh nghiệp',
|
||||
5: 'SLA được đàm phán bởi Dify Partners',
|
||||
0: 'Giải pháp triển khai có thể mở rộng cấp doanh nghiệp',
|
||||
7: 'Cập nhật và bảo trì bởi Dify chính thức',
|
||||
2: 'Các tính năng dành riêng cho doanh nghiệp',
|
||||
},
|
||||
price: 'Tùy chỉnh',
|
||||
for: 'Dành cho các đội lớn',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
0: 'Tất cả các tính năng cốt lõi được phát hành trong kho lưu trữ công cộng',
|
||||
2: 'Tuân thủ Giấy phép nguồn mở Dify',
|
||||
1: 'Không gian làm việc đơn',
|
||||
},
|
||||
description: 'Dành cho người dùng cá nhân, nhóm nhỏ hoặc các dự án phi thương mại',
|
||||
name: 'Cộng đồng',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
1: 'Không gian làm việc đơn',
|
||||
3: 'Hỗ trợ email & trò chuyện ưu tiên',
|
||||
0: 'Độ tin cậy tự quản lý của các nhà cung cấp đám mây khác nhau',
|
||||
2: 'Logo WebApp & Tùy chỉnh thương hiệu',
|
||||
},
|
||||
comingSoon: 'Hỗ trợ Microsoft Azure & Google Cloud Sẽ Đến Sớm',
|
||||
priceTip: 'Dựa trên Thị trường Đám mây',
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: 'Đề xuất',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: 'Chỉ mục Ngược',
|
||||
description: 'Chỉ mục Ngược là một cấu trúc được sử dụng cho việc truy xuất hiệu quả. Nó được tổ chức theo thuật ngữ, mỗi thuật ngữ trỏ đến tài liệu hoặc trang web chứa nó.',
|
||||
},
|
||||
change: 'Thay đổi',
|
||||
changeRetrievalMethod: 'Thay đổi phương pháp truy xuất',
|
||||
|
||||
@@ -8,7 +8,6 @@ const translation = {
|
||||
viewBilling: '管理賬單及訂閱',
|
||||
buyPermissionDeniedTip: '請聯絡企業管理員訂閱',
|
||||
plansCommon: {
|
||||
title: '選擇適合您的套餐',
|
||||
yearlyTip: '訂閱年度計劃可免費獲得 2 個月!',
|
||||
mostPopular: '最受歡迎',
|
||||
planRange: {
|
||||
@@ -131,15 +130,6 @@ const translation = {
|
||||
description: '獲得大規模關鍵任務系統的完整功能和支援。',
|
||||
includesTitle: 'Team 計劃中的一切,加上:',
|
||||
features: {
|
||||
8: '專業技術支持',
|
||||
6: '進階安全與控制',
|
||||
2: '獨家企業功能',
|
||||
5: 'Dify 合作夥伴協商的 SLA',
|
||||
4: '單一登入',
|
||||
7: 'Dify 官方更新和維護',
|
||||
3: '多個工作區和企業管理',
|
||||
0: '企業級可擴展部署解決方案',
|
||||
1: '商業許可證授權',
|
||||
},
|
||||
price: '自訂',
|
||||
btnText: '聯繫銷售',
|
||||
@@ -148,9 +138,6 @@ const translation = {
|
||||
},
|
||||
community: {
|
||||
features: {
|
||||
1: '單一工作區',
|
||||
2: '符合 Dify 開源許可證',
|
||||
0: '所有核心功能在公共存儲庫下發布',
|
||||
},
|
||||
includesTitle: '免費功能:',
|
||||
btnText: '開始使用社區',
|
||||
@@ -161,10 +148,6 @@ const translation = {
|
||||
},
|
||||
premium: {
|
||||
features: {
|
||||
0: '各種雲端供應商的自我管理可靠性',
|
||||
1: '單一工作區',
|
||||
3: '優先電子郵件和聊天支持',
|
||||
2: 'WebApp 標誌和品牌定制',
|
||||
},
|
||||
for: '適用於中型組織和團隊',
|
||||
comingSoon: '微軟 Azure 與 Google Cloud 支持即將推出',
|
||||
|
||||
@@ -36,8 +36,6 @@ const translation = {
|
||||
recommend: '推薦',
|
||||
},
|
||||
invertedIndex: {
|
||||
title: '倒排索引',
|
||||
description: '倒排索引是一種用於高效檢索的結構。按術語組織,每個術語指向包含它的文件或網頁',
|
||||
},
|
||||
change: '更改',
|
||||
changeRetrievalMethod: '更改檢索方法',
|
||||
|
||||
Reference in New Issue
Block a user