Skip to content

Commit 925fcab

Browse files
committed
feat: support i18n for error msg
1 parent bbc55cf commit 925fcab

21 files changed

+188
-71
lines changed

src/locale/locale.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"zh_CN": {
3+
"stmtInComplete": "语句不完整",
4+
"noValidPosition": "在此位置无效",
5+
"expecting": ",期望",
6+
"unfinishedMultilineComment": "未完成的多行注释",
7+
"unfinishedDoubleQuoted": "未完成的双引号字符串文字",
8+
"unfinishedSingleQuoted": "未完成的单引号字符串文字",
9+
"unfinishedTickQuoted": "未完成的反引号引用字符串文字",
10+
"noValidInput": "根本没有有效的输入",
11+
"newObj": "一个新的对象",
12+
"existingObj": "一个存在的对象",
13+
"new": "一个新的",
14+
"existing": "一个存在的",
15+
"orKeyword": "或者一个关键字",
16+
"keyword": "一个关键字",
17+
"missing": "缺少",
18+
"at": ""
19+
},
20+
"en_US": {
21+
"stmtInComplete": "statement is incomplete",
22+
"noValidPosition": "is not valid at this position",
23+
"expecting": ", expecting ",
24+
"unfinishedMultilineComment": "Unfinished multiline comment",
25+
"unfinishedDoubleQuoted": "Unfinished double quoted string literal",
26+
"unfinishedSingleQuoted": "Unfinished single quoted string literal",
27+
"unfinishedTickQuoted": "Unfinished back tick quoted string literal",
28+
"noValidInput": "is no valid input at all",
29+
"newObj": "a new object",
30+
"existingObj": "an existing object",
31+
"new": "a new ",
32+
"existing": "an existing ",
33+
"orKeyword": " or a keyword",
34+
"keyword": "a keyword",
35+
"missing": "missing ",
36+
"at": " at "
37+
}
38+
}

src/parser/common/basicSQL.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { CandidatesCollection, CodeCompletionCore } from 'antlr4-c3';
1414
import { SQLParserBase } from '../../lib/SQLParserBase';
1515
import { findCaretTokenIndex } from './findCaretTokenIndex';
1616
import { ctxToText, tokenToWord, WordRange, TextSlice } from './textAndWord';
17-
import { CaretPosition, Suggestions, SyntaxSuggestion } from './types';
17+
import { CaretPosition, LOCALE_TYPE, Suggestions, SyntaxSuggestion } from './types';
1818
import { ParseError, ErrorListener } from './parseErrorListener';
1919
import { ErrorStrategy } from './errorStrategy';
2020
import type { SplitListener } from './splitListener';
@@ -92,6 +92,8 @@ export abstract class BasicSQL<
9292
caretTokenIndex?: number
9393
): EntityCollector;
9494

95+
public locale: LOCALE_TYPE = 'en_US';
96+
9597
/**
9698
* Create an antlr4 lexer from input.
9799
* @param input string

src/parser/common/parseErrorListener.ts

+29-12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
InputMismatchException,
1111
NoViableAltException,
1212
} from 'antlr4ng';
13+
import { LOCALE_TYPE } from './types';
14+
import { transformToI18n } from './transformToI18n';
1315

1416
/**
1517
* Converted from {@link SyntaxError}.
@@ -46,8 +48,10 @@ export type ErrorListener = (parseError: ParseError, originalError: SyntaxError)
4648

4749
export abstract class ParseErrorListener implements ANTLRErrorListener {
4850
private _errorListener: ErrorListener;
51+
private locale: LOCALE_TYPE;
4952

50-
constructor(errorListener: ErrorListener) {
53+
constructor(errorListener: ErrorListener, locale: LOCALE_TYPE = 'en_US') {
54+
this.locale = locale;
5155
this._errorListener = errorListener;
5256
}
5357

@@ -88,20 +92,32 @@ export abstract class ParseErrorListener implements ANTLRErrorListener {
8892
// handle missing or unwanted tokens.
8993
message = msg;
9094
if (msg.includes('extraneous')) {
91-
message = `'${wrongText}' is not valid at this position${
92-
expectedText.length ? `, expecting ${expectedText}` : ''
95+
message = `'${wrongText}' {noValidPosition}${
96+
expectedText.length ? `{expecting}${expectedText}` : ''
9397
}`;
9498
}
99+
if (msg.includes('missing')) {
100+
const regex = /missing\s+'([^']+)'/;
101+
const match = msg.match(regex);
102+
message = `{missing}`;
103+
if (match) {
104+
const missKeyword = match[1];
105+
message += `'${missKeyword}'`;
106+
} else {
107+
message += `{keyword}`;
108+
}
109+
message += `{at}'${wrongText}'`;
110+
}
95111
} else {
96112
// handle mismatch exception or no viable alt exception
97113
if (e instanceof InputMismatchException || e instanceof NoViableAltException) {
98114
if (isEof) {
99-
message = `statement is incomplete`;
115+
message = `{stmtInComplete}`;
100116
} else {
101-
message = `'${wrongText}' is not valid at this position`;
117+
message = `'${wrongText}' {noValidPosition}`;
102118
}
103119
if (expectedText.length > 0) {
104-
message += `, expecting ${expectedText}`;
120+
message += `{expecting}${expectedText}`;
105121
}
106122
} else {
107123
message = msg;
@@ -117,24 +133,25 @@ export abstract class ParseErrorListener implements ANTLRErrorListener {
117133
);
118134
switch (text[0]) {
119135
case '/':
120-
message = 'Unfinished multiline comment';
136+
message = '{unfinishedMultilineComment}';
121137
break;
122138
case '"':
123-
message = 'Unfinished double quoted string literal';
139+
message = '{unfinishedDoubleQuoted}';
124140
break;
125141
case "'":
126-
message = 'Unfinished single quoted string literal';
142+
message = '{unfinishedSingleQuoted}';
127143
break;
128144
case '`':
129-
message = 'Unfinished back tick quoted string literal';
145+
message = '{unfinishedTickQuoted}';
130146
break;
131147

132148
default:
133-
message = '"' + text + '" is no valid input at all';
149+
message = '"' + text + '" {noValidInput}';
134150
break;
135151
}
136152
}
137153
}
154+
message = transformToI18n(message, this.locale);
138155
let endCol = charPositionInLine + 1;
139156
if (offendingSymbol && offendingSymbol.text !== null) {
140157
endCol = charPositionInLine + offendingSymbol.text.length;
@@ -146,7 +163,7 @@ export abstract class ParseErrorListener implements ANTLRErrorListener {
146163
endLine: line,
147164
startColumn: charPositionInLine + 1,
148165
endColumn: endCol + 1,
149-
message: message,
166+
message,
150167
},
151168
{
152169
e,

src/parser/common/transformToI18n.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { LOCALE_TYPE } from './types';
2+
import i18n from '../../locale/locale.json';
3+
4+
/**
5+
* transform message to locale language
6+
* @param message error msg
7+
* @param locale language setting
8+
*/
9+
function transformToI18n(message: string, locale: LOCALE_TYPE) {
10+
const regex = /{([^}]+)}/g;
11+
return message.replace(
12+
regex,
13+
(_, key: keyof (typeof i18n)[typeof locale]) => i18n[locale][key] || ''
14+
);
15+
}
16+
17+
export { transformToI18n };

src/parser/common/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ export interface Suggestions<T = WordRange> {
6767
*/
6868
readonly keywords: string[];
6969
}
70+
71+
export type LOCALE_TYPE = 'zh_CN' | 'en_US';

src/parser/flink/flinkErrorListener.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CodeCompletionCore } from 'antlr4-c3';
22
import { ErrorListener, ParseErrorListener } from '../common/parseErrorListener';
33
import { Parser, Token } from 'antlr4ng';
44
import { FlinkSqlParser } from '../../lib/flink/FlinkSqlParser';
5+
import { LOCALE_TYPE } from '../common/types';
56

67
export class FlinkErrorListener extends ParseErrorListener {
78
private preferredRules: Set<number>;
@@ -20,8 +21,8 @@ export class FlinkErrorListener extends ParseErrorListener {
2021
[FlinkSqlParser.RULE_columnNameCreate, 'column'],
2122
]);
2223

23-
constructor(errorListener: ErrorListener, preferredRules: Set<number>) {
24-
super(errorListener);
24+
constructor(errorListener: ErrorListener, preferredRules: Set<number>, locale: LOCALE_TYPE) {
25+
super(errorListener, locale);
2526
this.preferredRules = preferredRules;
2627
}
2728

@@ -49,9 +50,9 @@ export class FlinkErrorListener extends ParseErrorListener {
4950
case FlinkSqlParser.RULE_functionName:
5051
case FlinkSqlParser.RULE_columnName: {
5152
if (!name) {
52-
expectedText = 'a new object name';
53+
expectedText = '{newObj}';
5354
} else {
54-
expectedText = `a new ${name} name`;
55+
expectedText = `{new}${name}`;
5556
}
5657
break;
5758
}
@@ -61,17 +62,17 @@ export class FlinkErrorListener extends ParseErrorListener {
6162
case FlinkSqlParser.RULE_viewPathCreate:
6263
case FlinkSqlParser.RULE_columnNameCreate: {
6364
if (!name) {
64-
expectedText = 'an existing object';
65+
expectedText = '{existingObj}';
6566
} else {
66-
expectedText = `an existing ${name}`;
67+
expectedText = `{existing}${name}`;
6768
}
6869
break;
6970
}
7071
}
7172
}
7273
}
7374
if (candidates.tokens.size) {
74-
expectedText += expectedText ? ' or a keyword' : 'a keyword';
75+
expectedText += expectedText ? '{orKeyword}' : '{keyword}';
7576
}
7677
return expectedText;
7778
}

src/parser/flink/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class FlinkSQL extends BasicSQL<FlinkSqlLexer, ProgramContext, FlinkSqlPa
4040
}
4141

4242
protected createErrorListener(_errorListener: ErrorListener) {
43-
return new FlinkErrorListener(_errorListener, this.preferredRules);
43+
return new FlinkErrorListener(_errorListener, this.preferredRules, this.locale);
4444
}
4545

4646
protected createEntityCollector(input: string, caretTokenIndex?: number) {

src/parser/hive/hiveErrorListener.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CodeCompletionCore } from 'antlr4-c3';
22
import { ErrorListener, ParseErrorListener } from '../common/parseErrorListener';
33
import { Parser, Token } from 'antlr4ng';
44
import { HiveSqlParser } from '../../lib/hive/HiveSqlParser';
5+
import { LOCALE_TYPE } from '../common/types';
56

67
export class HiveErrorListener extends ParseErrorListener {
78
private preferredRules: Set<number>;
@@ -20,8 +21,8 @@ export class HiveErrorListener extends ParseErrorListener {
2021
[HiveSqlParser.RULE_columnNameCreate, 'column'],
2122
]);
2223

23-
constructor(errorListener: ErrorListener, preferredRules: Set<number>) {
24-
super(errorListener);
24+
constructor(errorListener: ErrorListener, preferredRules: Set<number>, locale: LOCALE_TYPE) {
25+
super(errorListener, locale);
2526
this.preferredRules = preferredRules;
2627
}
2728

@@ -50,9 +51,9 @@ export class HiveErrorListener extends ParseErrorListener {
5051
case HiveSqlParser.RULE_functionNameForInvoke:
5152
case HiveSqlParser.RULE_columnName: {
5253
if (!name) {
53-
expectedText = 'a new object name';
54+
expectedText = '{newObj}';
5455
} else {
55-
expectedText = `a new ${name} name`;
56+
expectedText = `{new}${name}`;
5657
}
5758
break;
5859
}
@@ -62,17 +63,17 @@ export class HiveErrorListener extends ParseErrorListener {
6263
case HiveSqlParser.RULE_viewNameCreate:
6364
case HiveSqlParser.RULE_columnNameCreate: {
6465
if (!name) {
65-
expectedText = 'an existing object';
66+
expectedText = '{existingObj}';
6667
} else {
67-
expectedText = `an existing ${name}`;
68+
expectedText = `{existing}${name}`;
6869
}
6970
break;
7071
}
7172
}
7273
}
7374
}
7475
if (candidates.tokens.size) {
75-
expectedText += expectedText ? ' or a keyword' : 'a keyword';
76+
expectedText += expectedText ? '{orKeyword}' : '{keyword}';
7677
}
7778
return expectedText;
7879
}

src/parser/hive/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class HiveSQL extends BasicSQL<HiveSqlLexer, ProgramContext, HiveSqlParse
4141
}
4242

4343
protected createErrorListener(_errorListener: ErrorListener) {
44-
return new HiveErrorListener(_errorListener, this.preferredRules);
44+
return new HiveErrorListener(_errorListener, this.preferredRules, this.locale);
4545
}
4646

4747
protected createEntityCollector(input: string, caretTokenIndex?: number) {

src/parser/impala/ImpalaErrorListener.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CodeCompletionCore } from 'antlr4-c3';
22
import { ErrorListener, ParseErrorListener } from '../common/parseErrorListener';
33
import { Parser, Token } from 'antlr4ng';
44
import { ImpalaSqlParser } from '../../lib/impala/ImpalaSqlParser';
5+
import { LOCALE_TYPE } from '../common/types';
56

67
export class ImpalaErrorListener extends ParseErrorListener {
78
private preferredRules: Set<number>;
@@ -19,8 +20,8 @@ export class ImpalaErrorListener extends ParseErrorListener {
1920
[ImpalaSqlParser.RULE_columnNamePathCreate, 'column'],
2021
]);
2122

22-
constructor(errorListener: ErrorListener, preferredRules: Set<number>) {
23-
super(errorListener);
23+
constructor(errorListener: ErrorListener, preferredRules: Set<number>, locale: LOCALE_TYPE) {
24+
super(errorListener, locale);
2425
this.preferredRules = preferredRules;
2526
}
2627

@@ -48,9 +49,9 @@ export class ImpalaErrorListener extends ParseErrorListener {
4849
case ImpalaSqlParser.RULE_viewNamePath:
4950
case ImpalaSqlParser.RULE_columnNamePath: {
5051
if (!name) {
51-
expectedText = 'a new object name';
52+
expectedText = '{newObj}';
5253
} else {
53-
expectedText = `a new ${name} name`;
54+
expectedText = `{new}${name}`;
5455
}
5556
break;
5657
}
@@ -60,17 +61,17 @@ export class ImpalaErrorListener extends ParseErrorListener {
6061
case ImpalaSqlParser.RULE_viewNameCreate:
6162
case ImpalaSqlParser.RULE_columnNamePathCreate: {
6263
if (!name) {
63-
expectedText = 'an existing object';
64+
expectedText = '{existingObj}';
6465
} else {
65-
expectedText = `an existing ${name}`;
66+
expectedText = `{existing}${name}`;
6667
}
6768
break;
6869
}
6970
}
7071
}
7172
}
7273
if (candidates.tokens.size) {
73-
expectedText += expectedText ? ' or a keyword' : 'a keyword';
74+
expectedText += expectedText ? '{orKeyword}' : '{keyword}';
7475
}
7576
return expectedText;
7677
}

src/parser/impala/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class ImpalaSQL extends BasicSQL<ImpalaSqlLexer, ProgramContext, ImpalaSq
3939
}
4040

4141
protected createErrorListener(_errorListener: ErrorListener) {
42-
return new ImpalaErrorListener(_errorListener, this.preferredRules);
42+
return new ImpalaErrorListener(_errorListener, this.preferredRules, this.locale);
4343
}
4444

4545
protected createEntityCollector(input: string, caretTokenIndex?: number) {

src/parser/mysql/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class MySQL extends BasicSQL<MySqlLexer, ProgramContext, MySqlParser> {
3939
}
4040

4141
protected createErrorListener(_errorListener: ErrorListener) {
42-
return new MysqlErrorListener(_errorListener, this.preferredRules);
42+
return new MysqlErrorListener(_errorListener, this.preferredRules, this.locale);
4343
}
4444

4545
protected createEntityCollector(input: string, caretTokenIndex?: number) {

0 commit comments

Comments
 (0)