Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions scripts/utils/Translator/ChatGPTTranslator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ class ChatGPTTranslator extends Translator {
userPrompt: text,
});

if (this.validateTemplatePlaceholders(text, result) && this.validateTemplateHTML(text, result)) {
const fixedResult = this.fixChineseBracketsInMarkdown(result);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be cleaner architecturally to have post-translation validation and sanitation happen in the abstract parent Translator class, but in practice it doesn't matter since for now (and probably for the foreseeable future) we'll only have ChatGPT doing actual translations.


if (this.validateTemplatePlaceholders(text, fixedResult) && this.validateTemplateHTML(text, fixedResult)) {
if (attempt > 0) {
console.log(`🙃 Translation succeeded after ${attempt + 1} attempts`);
}
return result;
return fixedResult;
}

console.warn(`⚠️ Translation for "${text}" failed validation (attempt ${attempt + 1}/${ChatGPTTranslator.MAX_RETRIES + 1})`);
Expand Down
9 changes: 9 additions & 0 deletions scripts/utils/Translator/Translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ abstract class Translator {
return `${text.slice(0, 80)}${text.length > 80 ? '...' : ''}`;
}

/**
* Replace Chinese full-width brackets 【 (U+3010) and 】 (U+3011) with normal ASCII brackets [ and ]
*/
public fixChineseBracketsInMarkdown(text: string): string {
return text
.replaceAll('\u3010', '[') // 【
.replaceAll('\u3011', ']'); // 】
}

/**
* Validate that placeholders are all present and unchanged before and after translation.
*/
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/ChatGPTTranslatorTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,17 @@ describe('ChatGPTTranslator.performTranslation', () => {
expect(MockedOpenAIUtils.prototype.promptChatCompletions).toHaveBeenCalledTimes(2);
expect(result).toBe(validHTMLTranslation);
});

it('fixes Chinese brackets in markdown syntax after translation', async () => {
const originalText = '[Click here](https://example.com)';
const translatedWithChineseBrackets = '【点击这里】(https://example.com)';
const expectedFixed = '[点击这里](https://example.com)';

(MockedOpenAIUtils.prototype.promptChatCompletions as jest.Mock).mockResolvedValueOnce(translatedWithChineseBrackets);

// @ts-expect-error TS2445
const result = await translator.performTranslation(targetLang, originalText);

expect(result).toBe(expectedFixed);
});
});
119 changes: 119 additions & 0 deletions tests/unit/TranslatorTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Translator from '@scripts/utils/Translator/Translator';

class TestTranslator extends Translator {
protected performTranslation(): Promise<string> {
return Promise.resolve('');
}
}

describe('Translator - Chinese Brackets Fix', () => {
let translator: TestTranslator;

beforeEach(() => {
translator = new TestTranslator();
});

describe('fixChineseBracketsInMarkdown', () => {
it('should fix Chinese brackets in markdown links', () => {
const input = '【Click here】(https://example.com)';
const expected = '[Click here](https://example.com)';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should fix Chinese brackets in markdown images', () => {
const input = '!【Alt text】(https://example.com/image.png)';
const expected = '![Alt text](https://example.com/image.png)';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should fix Chinese brackets in reference-style links', () => {
const input = '【Link text】【ref-id】';
const expected = '[Link text][ref-id]';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should fix Chinese brackets in reference definitions', () => {
const input = '【ref-id】: https://example.com "Title"';
const expected = '[ref-id]: https://example.com "Title"';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should fix Chinese brackets for URLs', () => {
const input = '【https://example.com】';
const expected = '[https://example.com]';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should fix Chinese brackets in task list items', () => {
const input = '- 【x】 Completed task\n- 【 】 Incomplete task';
const expected = '- [x] Completed task\n- [ ] Incomplete task';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should handle multiple markdown patterns in one text', () => {
const input = `
Check out 【this link】(https://example.com) and this image !【screenshot】(image.png).
Also see 【reference】【1】 and the definition:
【1】: https://reference.com

Tasks:
- 【x】 Done
- 【 】 Todo
`.trim();

const expected = `
Check out [this link](https://example.com) and this image ![screenshot](image.png).
Also see [reference][1] and the definition:
[1]: https://reference.com

Tasks:
- [x] Done
- [ ] Todo
`.trim();

const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should return original text if no Chinese brackets present', () => {
const input = '[Normal link](https://example.com) and regular text';
const expected = '[Normal link](https://example.com) and regular text';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should handle empty string', () => {
const input = '';
const expected = '';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should handle nested markdown structures', () => {
const input = '【【nested】】(url)';
const expected = '[[nested]](url)';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should handle task lists with different bullet points', () => {
const input = '* 【x】 Task 1\n+ 【 】 Task 2\n- 【x】 Task 3';
const expected = '* [x] Task 1\n+ [ ] Task 2\n- [x] Task 3';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});

it('should handle indented task lists', () => {
const input = ' - 【x】 Indented task\n - 【 】 More indented';
const expected = ' - [x] Indented task\n - [ ] More indented';
const result = translator.fixChineseBracketsInMarkdown(input);
expect(result).toBe(expected);
});
});
});
Loading