Просмотр исходного кода

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

Signed-off-by: feifei <[email protected]>
feifei 9 месяцев назад
Родитель
Сommit
cf487fbfae

+ 55 - 27
scripts/find-missing-i18n-key.js

@@ -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,21 +148,34 @@ 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)
+			})
+
+			if (!localeExists) {
+				console.error(`Error: Language '${args.locale}' not found in any locales directory`)
+				process.exit(1)
+			}
 		}
 
-		console.log(`Checking ${localeDirs.length} non-English languages: ${localeDirs.join(", ")}`)
-
 		const missingKeys = findMissingI18nKeys()
 
 		if (missingKeys.length === 0) {

+ 1 - 1
src/core/webview/ClineProvider.ts

@@ -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
 						}
 

+ 1 - 1
src/core/webview/__tests__/ClineProvider.test.ts

@@ -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 () => {

+ 2 - 0
webview-ui/src/i18n/locales/ca/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/de/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/en/chat.json

@@ -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"

+ 2 - 0
webview-ui/src/i18n/locales/es/chat.json

@@ -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"

+ 2 - 0
webview-ui/src/i18n/locales/fr/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/hi/chat.json

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

+ 2 - 0
webview-ui/src/i18n/locales/it/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/ja/chat.json

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

+ 2 - 0
webview-ui/src/i18n/locales/ko/chat.json

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

+ 2 - 0
webview-ui/src/i18n/locales/pl/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/tr/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/vi/chat.json

@@ -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}}",

+ 2 - 0
webview-ui/src/i18n/locales/zh-CN/chat.json

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

+ 2 - 0
webview-ui/src/i18n/locales/zh-TW/chat.json

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