Skip to content

Commit 2934f63

Browse files
authored
Add textlint to improve the translation process (#42)
* Add package to use textlint in git precommit hook textlint-tester is based on mocha. So I added mocha as test runner. * Add no-endline-colon rule colon and semi-colon both are applied. * Add textlint test step in CircleCI * Add terminology rule * Add README about textlint
1 parent 6fd7563 commit 2934f63

11 files changed

+1147
-54
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ jobs:
1818
- run:
1919
name: Check Prettier, ESLint, Flow
2020
command: yarn ci-check
21+
- run:
22+
name: Test Textlint
23+
command: yarn test:textlint

.textlintrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
filters: {
3+
comments: true,
4+
},
5+
formatterName: 'stylish',
6+
};

husky.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
hooks: {
3+
'pre-commit': 'lint-staged',
4+
},
5+
};

lint-staged.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
'*.md': ['textlint --rulesdir textlint/rules'],
3+
};

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,21 @@
8585
"nit:examples": "prettier --config examples/.prettierrc --list-different \"examples/**/*.js\"",
8686
"prettier": "yarn format:source && yarn format:examples",
8787
"prettier:diff": "yarn nit:source && yarn nit:examples",
88-
"reset": "rimraf ./.cache"
88+
"reset": "rimraf ./.cache",
89+
"test:textlint": "mocha textlint/tests/**/*.spec.js"
8990
},
9091
"devDependencies": {
9192
"@babel/preset-flow": "^7.0.0",
9293
"eslint-config-prettier": "^2.6.0",
94+
"husky": "^1.3.1",
95+
"lint-staged": "^8.1.4",
9396
"lz-string": "^1.4.4",
97+
"mocha": "^6.0.0",
9498
"npm-run-all": "^4.1.5",
9599
"recursive-readdir": "^2.2.1",
100+
"textlint": "^11.2.3",
101+
"textlint-filter-rule-comments": "^1.2.2",
102+
"textlint-tester": "^5.1.4",
96103
"unist-util-map": "^1.0.3"
97104
}
98105
}

textlint/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# textlint for ko.reactjs.org
2+
3+
React 공식 페이지 한국어 번역 시 활용하는 [textlint](https://textlint.github.io/)에 대해 설명합니다.
4+
5+
## 무엇인가요?
6+
7+
![textlint lint result](https://user-images.githubusercontent.com/7760903/53157203-58da9580-3604-11e9-88dc-59b96b01fe66.png)
8+
9+
textlint는 텍스트와 마크다운을 위한 linter이며 JavaScript로 구현되어 있습니다. [ESLint](https://eslint.org/)가 JavaScript에 가지는 역할과 같습니다.
10+
11+
## 특정 문맥에서 비활성화할 수 있나요?
12+
13+
[Filter Rule](https://textlint.github.io/docs/configuring.html#filter-rule) 중 하나인 [textlint-filter-rule-comments](https://github.com/textlint/textlint-filter-rule-comments)를 사용해서 비활성화할 수 있습니다. 미리 추가해놨으니 아래처럼 사용하시면 됩니다.
14+
15+
```md
16+
<!-- textlint-disable -->
17+
18+
주석 사이에 있는 글은 모든 규칙이 비활성화됩니다.
19+
20+
<!-- textlint-enable -->
21+
```
22+
23+
## 새로운 규칙(rule)을 어떻게 만드나요?
24+
25+
[textlint의 공식 문서 Creating Rules](https://textlint.github.io/docs/rule.html)를 숙지하고 다음 과정을 진행해주세요. 모든 코드는 `textlint` 폴더에서 작성됩니다.
26+
27+
- **`rules` 폴더에 1개의 규칙에 1개의 파일 생성**
28+
29+
커맨드 라인의 `--rulesdir` 옵션을 통해 실행되므로 `rules` 폴더 하위에는 규칙과 파일을 대응시켜서 작성해주세요.
30+
31+
- **`tests` 폴더에 테스트 코드 작성**
32+
33+
[`textlint-tester`](https://github.com/textlint/textlint/tree/master/packages/textlint-tester)를 활용해서 작성한 규칙에 대응되는 테스트를 작성해주세요. 올바른 사례와 올바르지 못한 사용 사례를 포함하고 올바르지 못한 사례는 번역자가 빠르게 수정할 수 있도록 `index`를 통해 오류가 발생한 위치를 알맞게 안내하고 있는지 검증해주세요.
34+
35+
아래처럼 실행한다면 모든 규칙 구현에 대한 테스트를 실행할 수 있습니다.
36+
37+
```
38+
$ yarn test:textlint
39+
```
40+
41+
## 주의해야 할 사항이 있나요?
42+
43+
- 모든 글이 번역된 상태가 아니며 번역이 완료되어도 새로운 글은 계속해서 번역이 되어야 하기 때문에 git pre-commit hook에서만 textlint를 실행하며 전체 마크다운 파일을 대상으로 CI에서 실행할 계획은 없습니다. 규칙의 구현에 대한 테스트는 CI에서 실행됩니다.
44+
- `--fix`를 통해 자동으로 수정할 수 있는 [Fixable Rule](https://textlint.github.io/docs/rule-fixable.html)은 의도적으로 작성하지 않습니다. 사람이 코드로 작성한 규칙이기 때문에 완벽하지 않으며 번역자가 인지하지 못한 채로 수정되기보다 문맥을 확인하고 수정하는 방향이 바람직하다고 생각하기 때문입니다.

textlint/rules/no-endline-colon.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* 문장 끝 쌍점(:)과 쌍반점(;)에 대한 규칙
3+
*/
4+
module.exports = function(context) {
5+
const {Syntax} = context;
6+
return {
7+
[Syntax.Str](node) {
8+
const {getSource, RuleError, report} = context;
9+
const text = getSource(node);
10+
const match = text.match(/[:;]$/);
11+
if (match) {
12+
report(
13+
node,
14+
new RuleError('문장 끝에 쌍점(:)과 쌍반점(;)은 사용하지 않습니다', {
15+
index: match.index,
16+
}),
17+
);
18+
}
19+
},
20+
};
21+
};

textlint/rules/terminology.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* 외래어 표기 및 약속된 용어를 위한 규칙
3+
*/
4+
module.exports = function(context) {
5+
const {Syntax} = context;
6+
return {
7+
[Syntax.Str](node) {
8+
const {getSource, RuleError, report} = context;
9+
const text = getSource(node);
10+
11+
for (const term of terms) {
12+
for (const expression of term.expressions) {
13+
let result;
14+
while ((result = expression.exec(text))) {
15+
report(
16+
node,
17+
new RuleError(term.message, {
18+
index: result.index,
19+
}),
20+
);
21+
}
22+
}
23+
}
24+
},
25+
};
26+
};
27+
28+
/**
29+
* 전역 검색을 위한 일련의 정규표현식을 생성합니다.
30+
* @param {RegExp[]} args
31+
* @return {RegExp[]} 'g' 플래그가 설정된 일련의 정규표현식
32+
*/
33+
const g = args => args.map(arg => new RegExp(arg, 'g'));
34+
35+
/**
36+
* @typedef {Object} Terminology
37+
* @property {string} value - 올바른 용어
38+
* @property {RegExp[]} expressions - 올바르지 못한 용어에 대한 일련의 정규표현식
39+
* @property {string} message - 에러 메시지
40+
*
41+
* @type {Terminology[]}
42+
*/
43+
const terms = [
44+
{
45+
value: '메서드',
46+
expressions: [//, //],
47+
message: 'method는 메서드가 올바른 표현입니다',
48+
},
49+
{
50+
value: '서드파티',
51+
expressions: [//],
52+
message: 'third party는 서드파티가 올바른 표현입니다',
53+
},
54+
].map(term => ({...term, expressions: g(term.expressions)}));
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const TextLintTester = require('textlint-tester');
2+
const rule = require('../rules/no-endline-colon');
3+
4+
const tester = new TextLintTester();
5+
6+
tester.run('no-endline-colon', rule, {
7+
valid: ['아래와 같습니다.', '제목: 설명입니다.'],
8+
invalid: [
9+
{
10+
text: '아래와 같습니다:',
11+
errors: [{index: 8}],
12+
},
13+
{
14+
text: '아래와 같습니다;',
15+
errors: [{index: 8}],
16+
},
17+
{
18+
text: '여러 줄일 때\n아래와 같습니다:\n',
19+
errors: [{index: 16}],
20+
},
21+
],
22+
});

textlint/tests/terminology.spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const TextLintTester = require('textlint-tester');
2+
const rule = require('../rules/terminology');
3+
4+
const tester = new TextLintTester();
5+
6+
tester.run('terminology', rule, {
7+
valid: ['메서드', '서드파티'],
8+
invalid: [
9+
{
10+
text: '한 문장에 연속하는 용어 메소드와 메소드와 메쏘드를 테스트합니다.',
11+
errors: [{index: 14}, {index: 19}, {index: 24}],
12+
},
13+
{
14+
text: '써드파티',
15+
errors: [{index: 0}],
16+
},
17+
],
18+
});

0 commit comments

Comments
 (0)