i18n Add translations for task pinning and unpinning (#2109)

Signed-off-by: feifei <liuyansheng1121@gmail.com>
This commit is contained in:
feifei 2025-03-30 23:42:48 +08:00 committed by GitHub
parent 2303f67afb
commit cf487fbfae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 87 additions and 29 deletions

View file

@ -34,9 +34,17 @@ Output:
process.exit(0)
}
// Directory to traverse
const TARGET_DIR = path.join(__dirname, "../webview-ui/src/components")
const LOCALES_DIR = path.join(__dirname, "../webview-ui/src/i18n/locales")
// Directories to traverse and their corresponding locales
const DIRS = {
components: {
path: path.join(__dirname, "../webview-ui/src/components"),
localesDir: path.join(__dirname, "../webview-ui/src/i18n/locales"),
},
src: {
path: path.join(__dirname, "../src"),
localesDir: path.join(__dirname, "../src/i18n/locales"),
},
}
// Regular expressions to match i18n keys
const i18nPatterns = [
@ -45,15 +53,23 @@ const i18nPatterns = [
/t\("([a-zA-Z][a-zA-Z0-9_]*[:.][a-zA-Z0-9_.]+)"\)/g, // Match t("key") format, where key contains a colon or dot
]
// Get all language directories
function getLocaleDirs() {
const allLocales = fs.readdirSync(LOCALES_DIR).filter((file) => {
const stats = fs.statSync(path.join(LOCALES_DIR, file))
return stats.isDirectory() // Do not exclude any language directories
})
// Get all language directories for a specific locales directory
function getLocaleDirs(localesDir) {
try {
const allLocales = fs.readdirSync(localesDir).filter((file) => {
const stats = fs.statSync(path.join(localesDir, file))
return stats.isDirectory() // Do not exclude any language directories
})
// Filter to a specific language if specified
return args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales
// Filter to a specific language if specified
return args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales
} catch (error) {
if (error.code === "ENOENT") {
console.warn(`Warning: Locales directory not found: ${localesDir}`)
return []
}
throw error
}
}
// Get the value from JSON by path
@ -72,14 +88,14 @@ function getValueByPath(obj, path) {
}
// Check if the key exists in all language files, return a list of missing language files
function checkKeyInLocales(key, localeDirs) {
function checkKeyInLocales(key, localeDirs, localesDir) {
const [file, ...pathParts] = key.split(":")
const jsonPath = pathParts.join(".")
const missingLocales = []
localeDirs.forEach((locale) => {
const filePath = path.join(LOCALES_DIR, locale, `${file}.json`)
const filePath = path.join(localesDir, locale, `${file}.json`)
if (!fs.existsSync(filePath)) {
missingLocales.push(`${locale}/${file}.json`)
return
@ -96,21 +112,20 @@ function checkKeyInLocales(key, localeDirs) {
// Recursively traverse the directory
function findMissingI18nKeys() {
const localeDirs = getLocaleDirs()
const results = []
function walk(dir) {
function walk(dir, baseDir, localeDirs, localesDir) {
const files = fs.readdirSync(dir)
for (const file of files) {
const filePath = path.join(dir, file)
const stat = fs.statSync(filePath)
// Exclude test files
if (filePath.includes(".test.")) continue
// Exclude test files and __mocks__ directory
if (filePath.includes(".test.") || filePath.includes("__mocks__")) continue
if (stat.isDirectory()) {
walk(filePath) // Recursively traverse subdirectories
walk(filePath, baseDir, localeDirs, localesDir) // Recursively traverse subdirectories
} else if (stat.isFile() && [".ts", ".tsx", ".js", ".jsx"].includes(path.extname(filePath))) {
const content = fs.readFileSync(filePath, "utf8")
@ -119,12 +134,12 @@ function findMissingI18nKeys() {
let match
while ((match = pattern.exec(content)) !== null) {
const key = match[1]
const missingLocales = checkKeyInLocales(key, localeDirs)
const missingLocales = checkKeyInLocales(key, localeDirs, localesDir)
if (missingLocales.length > 0) {
results.push({
key,
missingLocales,
file: path.relative(TARGET_DIR, filePath),
file: path.relative(baseDir, filePath),
})
}
}
@ -133,20 +148,33 @@ function findMissingI18nKeys() {
}
}
walk(TARGET_DIR)
// Walk through all directories
Object.entries(DIRS).forEach(([name, config]) => {
const localeDirs = getLocaleDirs(config.localesDir)
if (localeDirs.length > 0) {
console.log(`\nChecking ${name} directory with ${localeDirs.length} languages: ${localeDirs.join(", ")}`)
walk(config.path, config.path, localeDirs, config.localesDir)
}
})
return results
}
// Execute and output the results
function main() {
try {
const localeDirs = getLocaleDirs()
if (args.locale && localeDirs.length === 0) {
console.error(`Error: Language '${args.locale}' not found in ${LOCALES_DIR}`)
process.exit(1)
}
if (args.locale) {
// Check if the specified locale exists in any of the locales directories
const localeExists = Object.values(DIRS).some((config) => {
const localeDirs = getLocaleDirs(config.localesDir)
return localeDirs.includes(args.locale)
})
console.log(`Checking ${localeDirs.length} non-English languages: ${localeDirs.join(", ")}`)
if (!localeExists) {
console.error(`Error: Language '${args.locale}' not found in any locales directory`)
process.exit(1)
}
}
const missingKeys = findMissingI18nKeys()

View file

@ -1250,7 +1250,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
case "openProjectMcpSettings": {
if (!vscode.workspace.workspaceFolders?.length) {
vscode.window.showErrorMessage(t("common:no_workspace"))
vscode.window.showErrorMessage(t("common:errors.no_workspace"))
return
}

View file

@ -2031,7 +2031,7 @@ describe("Project MCP Settings", () => {
await messageHandler({ type: "openProjectMcpSettings" })
// Verify error message was shown
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("no_workspace")
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.no_workspace")
})
test.skip("handles openProjectMcpSettings file creation error", async () => {

View file

@ -12,6 +12,8 @@
"export": "Exportar historial de tasques",
"delete": "Eliminar tasca (Shift + Clic per ometre confirmació)"
},
"unpin": "desancorar",
"pin": "ancorar",
"tokenProgress": {
"availableSpace": "Espai disponible: {{amount}} tokens",
"tokensUsed": "Tokens utilitzats: {{used}} de {{total}}",

View file

@ -12,6 +12,8 @@
"export": "Aufgabenverlauf exportieren",
"delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)"
},
"unpin": "lösen",
"pin": "anheften",
"tokenProgress": {
"availableSpace": "Verfügbarer Speicher: {{amount}} Tokens",
"tokensUsed": "Verwendete Tokens: {{used}} von {{total}}",

View file

@ -12,6 +12,8 @@
"export": "Export task history",
"delete": "Delete Task (Shift + Click to skip confirmation)"
},
"unpin": "unpin",
"pin": "pin",
"retry": {
"title": "Retry",
"tooltip": "Try the operation again"

View file

@ -12,6 +12,8 @@
"export": "Exportar historial de tareas",
"delete": "Eliminar tarea (Shift + Clic para omitir confirmación)"
},
"unpin": "desanclar",
"pin": "anclar",
"retry": {
"title": "Reintentar",
"tooltip": "Intenta la operación de nuevo"

View file

@ -12,6 +12,8 @@
"export": "Exporter l'historique des tâches",
"delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)"
},
"unpin": "détacher",
"pin": "épingler",
"tokenProgress": {
"availableSpace": "Espace disponible : {{amount}} tokens",
"tokensUsed": "Tokens utilisés : {{used}} sur {{total}}",

View file

@ -12,6 +12,8 @@
"export": "कार्य इतिहास निर्यात करें",
"delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)"
},
"unpin": "पिन हटाएं",
"pin": "पिन करें",
"tokenProgress": {
"availableSpace": "उपलब्ध स्थान: {{amount}} tokens",
"tokensUsed": "प्रयुक्त tokens: {{used}} / {{total}}",

View file

@ -12,6 +12,8 @@
"export": "Esporta cronologia attività",
"delete": "Elimina attività (Shift + Clic per saltare la conferma)"
},
"unpin": "sblocca",
"pin": "blocca",
"tokenProgress": {
"availableSpace": "Spazio disponibile: {{amount}} tokens",
"tokensUsed": "Tokens utilizzati: {{used}} di {{total}}",

View file

@ -12,6 +12,8 @@
"export": "タスク履歴をエクスポート",
"delete": "タスクを削除Shift + クリックで確認をスキップ)"
},
"unpin": "固定解除",
"pin": "固定",
"tokenProgress": {
"availableSpace": "利用可能な空き容量: {{amount}} トークン",
"tokensUsed": "使用トークン: {{used}} / {{total}}",

View file

@ -12,6 +12,8 @@
"export": "작업 기록 내보내기",
"delete": "작업 삭제 (Shift + 클릭으로 확인 생략)"
},
"unpin": "고정 해제",
"pin": "고정",
"tokenProgress": {
"availableSpace": "사용 가능한 공간: {{amount}} 토큰",
"tokensUsed": "사용된 토큰: {{used}} / {{total}}",

View file

@ -12,6 +12,8 @@
"export": "Eksportuj historię zadań",
"delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)"
},
"unpin": "odepnij",
"pin": "przypnij",
"tokenProgress": {
"availableSpace": "Dostępne miejsce: {{amount}} tokenów",
"tokensUsed": "Wykorzystane tokeny: {{used}} z {{total}}",

View file

@ -12,6 +12,8 @@
"export": "Exportar histórico de tarefas",
"delete": "Excluir tarefa (Shift + Clique para pular confirmação)"
},
"unpin": "desafixar",
"pin": "fixar",
"tokenProgress": {
"availableSpace": "Espaço disponível: {{amount}} tokens",
"tokensUsed": "Tokens usados: {{used}} de {{total}}",

View file

@ -12,6 +12,8 @@
"export": "Görev geçmişini dışa aktar",
"delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)"
},
"unpin": "sabitlemeyi kaldır",
"pin": "sabitle",
"tokenProgress": {
"availableSpace": "Kullanılabilir alan: {{amount}} token",
"tokensUsed": "Kullanılan tokenlar: {{used}} / {{total}}",

View file

@ -12,6 +12,8 @@
"export": "Xuất lịch sử nhiệm vụ",
"delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)"
},
"unpin": "bỏ ghim",
"pin": "ghim",
"tokenProgress": {
"availableSpace": "Không gian khả dụng: {{amount}} tokens",
"tokensUsed": "Tokens đã sử dụng: {{used}} trong {{total}}",

View file

@ -12,6 +12,8 @@
"export": "导出任务历史",
"delete": "删除任务Shift + 点击跳过确认)"
},
"unpin": "取消置顶",
"pin": "置顶",
"tokenProgress": {
"availableSpace": "可用空间: {{amount}} tokens",
"tokensUsed": "已使用tokens: {{used}} / {{total}}",

View file

@ -12,6 +12,8 @@
"export": "匯出任務歷史",
"delete": "刪除任務Shift + 點擊跳過確認)"
},
"unpin": "取消置頂",
"pin": "置頂",
"tokenProgress": {
"availableSpace": "可用空間: {{amount}} tokens",
"tokensUsed": "已使用tokens: {{used}} / {{total}}",