✨ feat(vscode): 代码阅读关键字按语境翻译
This commit is contained in:
1 parent
72d33032f0
commit
d134837c17
5 files changed
+155
-19
No files matched your search
@@ -161,15 +161,17 @@ export interface CodeReaderApi {
|
||||
|
||||
其他规则:空数组字面量 → 空数组(Python 空列表 → 空列表、空字典 → 空字典);复合语句头部去掉结尾的 `{` 或 `:`;多行合成一行(连续空白压成一个空格,右括号前的尾逗号去掉);注释去掉注释符号,原文照搬。关键字与前后内容之间保留源码里的空格(`!` 翻成「非」后补一个空格)。
|
||||
|
||||
关键字按所在语法节点选择译法:同一个关键字在不同语法结构里意思不同时,表里写一个默认译法,再加若干「语境 → 译法」。语境是从关键字节点往上的节点类型链,外层在前:`raise_statement` 指父节点是 raise_statement,`for_statement > else_clause` 指父节点是 else_clause、祖父节点是 for_statement(关键字包在一层子句节点里时要看到祖父节点)。几条语境同时匹配时取最长的,都不匹配用默认译法。节点类型以 tree-sitter 语法为准。下面两张表里写成「(语境)译法 / (语境)译法」的关键字就是按这条规则区分的,排第一个的是默认译法。
|
||||
|
||||
TypeScript 关键字表(流程 = keyword,声明 = storage):
|
||||
|
||||
| 流程 | 声明 / 字面量 |
|
||||
| --- | --- |
|
||||
| import 导入、from 来自、as 作为、export 公共、default 默认、if 如果、else 否则、for 遍历、of 取自、in 在、while 当、do 执行、switch 分支、case 情况、break 跳出、continue 继续、return 返回、throw 抛出、try 尝试、catch 捕获、finally 最终、await 等待、yield 产出 | const 常量、let 变量、var 变量、function 函数、async 异步、class 类、extends 继承、implements 实现、interface 接口、type 类型、enum 枚举、new 新建、instanceof 属于、typeof 类型为、keyof 键名、readonly 只读、public 公共、private 私有、protected 受保护、static 静态、abstract 抽象、delete 删除、true 真、false 假、null 空、undefined 未定义 |
|
||||
| import 导入、from 来自、as(导入 / 导出别名)作为 / (类型断言 `x as T`、`as const`)断言为、export 公共、default(`export default`)默认 / (`switch` 里的 `default:`)默认情况、if 如果、else 否则、for 遍历、of 取自、in(二元运算 `'k' in obj`)在 / (`for…in` 头部)取自、while 当、do 执行、switch 分支、case 情况、break 跳出、continue 继续、return 返回、throw 抛出、try 尝试、catch 捕获、finally 最终、await 等待、yield 产出 | const 常量、let 变量、var 变量、function 函数、async 异步、class 类、extends(类 / 接口继承)继承 / (泛型约束 `T extends X`、`infer U extends X`)限定为 / (条件类型 `A extends B ? … : …`)符合、implements 实现、interface 接口、type 类型、enum 枚举、new 新建、instanceof 属于、typeof 类型为、keyof 键名、readonly 只读、public 公共、private 私有、protected 受保护、static 静态、abstract 抽象、delete 删除、true 真、false 假、null 空、undefined 未定义 |
|
||||
|
||||
TypeScript 内置类型:string 字符串、number 数值、boolean 布尔、any 任意、unknown 未知、never 永不、void 无、object 对象、bigint 大整数、symbol 符号。`Promise` 等库类型不翻。
|
||||
|
||||
Python 关键字:import 导入、from 从、as 作为、def 函数、async 异步、await 等待、class 类、return 返回、if 如果、elif 否则如果、else 否则、for 遍历、in(for 头部与推导式里)取自 / (比较里)在、while 当、try 尝试、except 捕获、finally 最终、raise 抛出、with 使用、yield 产出、lambda 匿名函数、pass 跳过、break 跳出、continue 继续、global 全局、nonlocal 非局部、del 删除、assert 断言、not 非、and 与、or 或、is 是、None 空、True 真、False 假、match 匹配、case 情况。
|
||||
Python 关键字:import 导入、from(导入)从 / (`raise … from`)源自 / (`yield from`)自、as(导入别名、`with … as`、`except … as`)作为、def 函数、async 异步、await 等待、class 类、return 返回、if 如果、elif 否则如果、else(`if` 后、条件表达式里)否则 / (`for` / `while` 后,循环没被 break 才执行)未中断时 / (`try` 后,没出错才执行)未出错时、for 遍历、in(比较里)在 / (for 头部与推导式里)取自、while 当、try 尝试、except 捕获、finally 最终、raise 抛出、with 使用、yield 产出、lambda 匿名函数、pass 跳过、break 跳出、continue 继续、global 全局、nonlocal 非局部、del 删除、assert 断言、not 非、and 与、or 或、is 是、not in 不在、is not 不是、None 空、True 真、False 假、match 匹配、case 情况。
|
||||
|
||||
Python 内置类型(仅注解里):str 字符串、int 整数、float 浮点数、bool 布尔、list 列表、dict 字典、set 集合、tuple 元组、bytes 字节串。
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const pythonZhTranslator: Translator = createZhTemplateTranslator({
|
||||
tokenRole: pythonTokenRole,
|
||||
keywords: {
|
||||
'import': '导入',
|
||||
'from': '从',
|
||||
'from': { zh: '从', byContext: { raise_statement: '源自', yield: '自' } },
|
||||
'as': '作为',
|
||||
'def': '函数',
|
||||
'async': '异步',
|
||||
@@ -29,9 +29,17 @@ export const pythonZhTranslator: Translator = createZhTemplateTranslator({
|
||||
'return': '返回',
|
||||
'if': '如果',
|
||||
'elif': '否则如果',
|
||||
'else': '否则',
|
||||
// 循环的 else 只在没被 break 打断时执行,try 的 else 只在没抛异常时执行
|
||||
'else': {
|
||||
zh: '否则',
|
||||
byContext: {
|
||||
'for_statement > else_clause': '未中断时',
|
||||
'while_statement > else_clause': '未中断时',
|
||||
'try_statement > else_clause': '未出错时',
|
||||
},
|
||||
},
|
||||
'for': '遍历',
|
||||
'in': { zh: '在', byParent: { for_statement: '取自', for_in_clause: '取自' } },
|
||||
'in': { zh: '在', byContext: { for_statement: '取自', for_in_clause: '取自' } },
|
||||
'while': '当',
|
||||
'try': '尝试',
|
||||
'except': '捕获',
|
||||
|
||||
@@ -23,14 +23,14 @@ export const typescriptZhTranslator: Translator = createZhTemplateTranslator({
|
||||
// 流程
|
||||
import: '导入',
|
||||
from: '来自',
|
||||
as: '作为',
|
||||
as: { zh: '作为', byContext: { as_expression: '断言为' } },
|
||||
export: '公共',
|
||||
default: '默认',
|
||||
default: { zh: '默认', byContext: { switch_default: '默认情况' } },
|
||||
if: '如果',
|
||||
else: '否则',
|
||||
for: '遍历',
|
||||
of: '取自',
|
||||
in: '在',
|
||||
in: { zh: '在', byContext: { for_in_statement: '取自' } },
|
||||
while: '当',
|
||||
do: '执行',
|
||||
switch: '分支',
|
||||
@@ -51,7 +51,8 @@ export const typescriptZhTranslator: Translator = createZhTemplateTranslator({
|
||||
function: '函数',
|
||||
async: '异步',
|
||||
class: '类',
|
||||
extends: '继承',
|
||||
// infer U extends X 同样是给类型参数加约束
|
||||
extends: { zh: '继承', byContext: { constraint: '限定为', infer_type: '限定为', conditional_type: '符合' } },
|
||||
implements: '实现',
|
||||
interface: '接口',
|
||||
type: '类型',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createTypeScriptAdapter } from '../languages/typescript'
|
||||
import { readFixture, walk, WASM_DIR, withBlocks } from '../testSupport'
|
||||
import { pythonZhTranslator } from './pythonZh'
|
||||
import { typescriptZhTranslator } from './typescriptZh'
|
||||
import { createZhTemplateTranslator } from './zhTemplate'
|
||||
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
const py = createPythonAdapter(WASM_DIR)
|
||||
@@ -124,6 +125,39 @@ describe('typeScript 中文直译', () => {
|
||||
].join('\n')
|
||||
expect(texts(await translateAll(ts, 'typescript', typescriptZhTranslator, source))[1]).toBe('call( first, 空数组 )')
|
||||
})
|
||||
|
||||
it('as / in / extends / default 按所在语法节点换译法', async () => {
|
||||
const source = [
|
||||
'import * as path from \'node:path\'',
|
||||
'export { run as start } from \'./run\'',
|
||||
'const v = x as string',
|
||||
'const k = [\'a\'] as const',
|
||||
'for (const key in obj) {}',
|
||||
'const has = \'k\' in obj',
|
||||
'class A extends B implements C {}',
|
||||
'interface I extends J, K {}',
|
||||
'function f<T extends X>(t: T) {}',
|
||||
'type R<T> = T extends string ? 1 : 2',
|
||||
'export default function g() {}',
|
||||
'switch (x) {',
|
||||
' default:',
|
||||
' break',
|
||||
'}',
|
||||
].join('\n')
|
||||
const map = texts(await translateAll(ts, 'typescript', typescriptZhTranslator, source))
|
||||
expect(map[1]).toBe('导入 * 作为 path 来自 \'node:path\'')
|
||||
expect(map[2]).toBe('公共 { run 作为 start } 来自 \'./run\'')
|
||||
expect(map[3]).toBe('常量 v = x 断言为 字符串')
|
||||
expect(map[4]).toBe('常量 k = [\'a\'] 断言为 常量')
|
||||
expect(map[5]).toBe('遍历 (常量 key 取自 obj)')
|
||||
expect(map[6]).toBe('常量 has = \'k\' 在 obj')
|
||||
expect(map[7]).toBe('类 A 继承 B 实现 C')
|
||||
expect(map[8]).toBe('接口 I 继承 J, K')
|
||||
expect(map[9]).toBe('函数 f<T 限定为 X>(t: T)')
|
||||
expect(map[10]).toBe('类型 R<T> = T 符合 字符串 ? 1 : 2')
|
||||
expect(map[11]).toBe('公共 默认 函数 g()')
|
||||
expect(map[13]).toBe('默认情况')
|
||||
})
|
||||
})
|
||||
|
||||
describe('python 中文直译', () => {
|
||||
@@ -152,7 +186,7 @@ describe('python 中文直译', () => {
|
||||
24: '使用 open(os.path.join(root, found[0])) 作为 handle',
|
||||
25: 'handle.read()',
|
||||
26: '捕获 OSError 作为 err',
|
||||
27: '抛出 RuntimeError(str(err)) 从 err',
|
||||
27: '抛出 RuntimeError(str(err)) 源自 err',
|
||||
28: '最终',
|
||||
29: 'skipped.clear()',
|
||||
30: '返回 found',
|
||||
@@ -177,14 +211,81 @@ describe('python 中文直译', () => {
|
||||
expect(map[2]).toBe('返回 [str(k) 遍历 k 取自 items 如果 k 不在 list]')
|
||||
})
|
||||
|
||||
it('from 在句首译成「从」,yield from 同一个词', async () => {
|
||||
it('from:导入里「从」,raise … from 里「源自」,yield from 里「自」', async () => {
|
||||
const source = [
|
||||
'from typing import Annotated',
|
||||
'def walk(nodes):',
|
||||
' yield from nodes',
|
||||
' raise RuntimeError(str(err)) from err',
|
||||
].join('\n')
|
||||
const map = texts(await translateAll(py, 'python', pythonZhTranslator, source))
|
||||
expect(map[1]).toBe('从 typing 导入 Annotated')
|
||||
expect(map[3]).toBe('产出 从 nodes')
|
||||
expect(map[3]).toBe('产出 自 nodes')
|
||||
expect(map[4]).toBe('抛出 RuntimeError(str(err)) 源自 err')
|
||||
})
|
||||
|
||||
it('else:if 后「否则」,for / while 后「未中断时」,try 后「未出错时」', async () => {
|
||||
const source = [
|
||||
'for k in items:',
|
||||
' if k:',
|
||||
' break',
|
||||
' else:',
|
||||
' pass',
|
||||
'else:',
|
||||
' pass',
|
||||
'while busy:',
|
||||
' pass',
|
||||
'else:',
|
||||
' pass',
|
||||
'try:',
|
||||
' pass',
|
||||
'except OSError:',
|
||||
' pass',
|
||||
'else:',
|
||||
' pass',
|
||||
].join('\n')
|
||||
const map = texts(await translateAll(py, 'python', pythonZhTranslator, source))
|
||||
expect(map[4]).toBe('否则')
|
||||
expect(map[6]).toBe('未中断时')
|
||||
expect(map[10]).toBe('未中断时')
|
||||
expect(map[16]).toBe('未出错时')
|
||||
})
|
||||
})
|
||||
|
||||
describe('中文模板引擎', () => {
|
||||
it('关键字按父节点类型选译法,更长的节点类型链优先,都不匹配用默认', async () => {
|
||||
const translator = createZhTemplateTranslator({
|
||||
id: 'test.zh-template',
|
||||
label: '测试',
|
||||
languageIds: ['python'],
|
||||
tokenRole: () => 'keyword',
|
||||
keywords: {
|
||||
in: { zh: '甲', byContext: { for_statement: '乙' } },
|
||||
else: { zh: '丙', byContext: { 'else_clause': '丁', 'for_statement > else_clause': '戊' } },
|
||||
},
|
||||
literals: {},
|
||||
builtinTypes: {},
|
||||
isTypePosition: () => false,
|
||||
operators: {},
|
||||
operatorParents: [],
|
||||
emptyLiterals: {},
|
||||
stringNodes: [],
|
||||
commentText: node => node.text,
|
||||
})
|
||||
const source = [
|
||||
'for k in items:',
|
||||
' if k in seen:',
|
||||
' pass',
|
||||
' else:',
|
||||
' x = a if c else b',
|
||||
'else:',
|
||||
' pass',
|
||||
].join('\n')
|
||||
const map = texts(await translateAll(py, 'python', translator, source))
|
||||
expect(map[1]).toBe('for k 乙 items')
|
||||
expect(map[2]).toBe('if k 甲 seen')
|
||||
expect(map[4]).toBe('丁')
|
||||
expect(map[5]).toBe('x = a if c 丙 b')
|
||||
expect(map[6]).toBe('戊')
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,16 @@
|
||||
import type { CodeBlock, Node, Segment, SourceSpan, TokenRole, Translator } from '../contract'
|
||||
|
||||
/** 按父节点类型区分译法的关键字,例如 Python 的 in:for 头部里「取自」,比较里「在」。 */
|
||||
export interface ContextualWord { zh: string, byParent: Record<string, string> }
|
||||
/** 按所在语法节点区分译法的关键字,例如 Python 的 else:if 后「否则」,for / while 后「未中断时」。 */
|
||||
export interface ContextualWord {
|
||||
/** 没有匹配的语境时用的译法。 */
|
||||
zh: string
|
||||
/**
|
||||
* 语境 → 译法。语境是关键字节点往上的节点类型链,外层在前、用 ` > ` 连接:
|
||||
* `raise_statement` 指父节点,`for_statement > else_clause` 指父节点 else_clause 且祖父节点 for_statement。
|
||||
* 同时匹配多条时取最长的那条。
|
||||
*/
|
||||
byContext: Record<string, string>
|
||||
}
|
||||
|
||||
/** 一种语言的中文直译表。引擎只替换表里的三类东西,其余原样照抄。 */
|
||||
export interface ZhTable {
|
||||
@@ -10,7 +19,7 @@ export interface ZhTable {
|
||||
languageIds: string[]
|
||||
/** 未翻译的词按源码着色的词性上色;关键字译文的词性也取它。 */
|
||||
tokenRole: (leaf: Node) => TokenRole
|
||||
/** 关键字:按语法树里匿名节点的类型查(Python 的 `not in` 这类组合关键字本身也是一个匿名节点)。 */
|
||||
/** 关键字:按语法树里匿名节点的类型查(Python 的 `not in` 这类组合关键字本身也是一个匿名节点);随所在语法节点换译法的写成 ContextualWord。 */
|
||||
keywords: Record<string, string | ContextualWord>
|
||||
/** 字面量关键字:具名的叶子节点(true / None …),按节点类型查。 */
|
||||
literals: Record<string, string>
|
||||
@@ -38,6 +47,7 @@ interface Token {
|
||||
raw: string
|
||||
}
|
||||
|
||||
const CONTEXT_SEPARATOR = ' > '
|
||||
const CLOSING = new Set([')', ']', '}'])
|
||||
const HEADER_OPENERS = new Set(['{', ':'])
|
||||
|
||||
@@ -70,6 +80,22 @@ function oneLine(text: string): string {
|
||||
export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
const operatorParents = new Set(table.operatorParents)
|
||||
const stringNodes = new Set(table.stringNodes)
|
||||
const contextDepth = Math.max(0, ...Object.values(table.keywords).flatMap(word =>
|
||||
typeof word === 'string' ? [] : Object.keys(word.byContext).map(context => context.split(CONTEXT_SEPARATOR).length)))
|
||||
|
||||
/** 从父节点往上逐层拼出语境去查,更长(更具体)的匹配覆盖更短的。 */
|
||||
function keywordText(word: string | ContextualWord, node: Node): string {
|
||||
if (typeof word === 'string')
|
||||
return word
|
||||
let text = word.zh
|
||||
let context = ''
|
||||
let ancestor = node.parent
|
||||
for (let level = 0; ancestor && level < contextDepth; level++, ancestor = ancestor.parent) {
|
||||
context = context ? `${ancestor.type}${CONTEXT_SEPARATOR}${context}` : ancestor.type
|
||||
text = word.byContext[context] ?? text
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
/** 能整个换成中文的节点 → 译文和词性。 */
|
||||
function translateNode(node: Node): { text: string, role: TokenRole } | undefined {
|
||||
@@ -83,10 +109,8 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
if (parent && operatorParents.has(parent.type) && table.operators[node.type])
|
||||
return { text: table.operators[node.type], role: 'operator' }
|
||||
const word = table.keywords[node.type]
|
||||
if (word !== undefined) {
|
||||
const text = typeof word === 'string' ? word : (parent && word.byParent[parent.type]) || word.zh
|
||||
return { text, role: table.tokenRole(firstLeaf(node)) }
|
||||
}
|
||||
if (word !== undefined)
|
||||
return { text: keywordText(word, node), role: table.tokenRole(firstLeaf(node)) }
|
||||
return undefined
|
||||
}
|
||||
if (node.childCount === 0 && table.literals[node.type])
|
||||
|
||||
Reference in new issue
Block a user