Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add textlint to improve the translation process #42

Merged
merged 5 commits into from
Feb 22, 2019
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
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ jobs:
- run:
name: Check Prettier, ESLint, Flow
command: yarn ci-check
- run:
name: Test Textlint
command: yarn test:textlint
6 changes: 6 additions & 0 deletions .textlintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
filters: {
comments: true,
},
formatterName: 'stylish',
};
5 changes: 5 additions & 0 deletions husky.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
hooks: {
'pre-commit': 'lint-staged',
},
};
3 changes: 3 additions & 0 deletions lint-staged.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
'*.md': ['textlint --rulesdir textlint/rules'],
};
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,21 @@
"nit:examples": "prettier --config examples/.prettierrc --list-different \"examples/**/*.js\"",
"prettier": "yarn format:source && yarn format:examples",
"prettier:diff": "yarn nit:source && yarn nit:examples",
"reset": "rimraf ./.cache"
"reset": "rimraf ./.cache",
"test:textlint": "mocha textlint/tests/**/*.spec.js"
},
"devDependencies": {
"@babel/preset-flow": "^7.0.0",
"eslint-config-prettier": "^2.6.0",
"husky": "^1.3.1",
"lint-staged": "^8.1.4",
"lz-string": "^1.4.4",
"mocha": "^6.0.0",
"npm-run-all": "^4.1.5",
"recursive-readdir": "^2.2.1",
"textlint": "^11.2.3",
"textlint-filter-rule-comments": "^1.2.2",
"textlint-tester": "^5.1.4",
"unist-util-map": "^1.0.3"
}
}
44 changes: 44 additions & 0 deletions textlint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# textlint for ko.reactjs.org

React 공식 페이지 한국어 번역 시 활용하는 [textlint](https://textlint.github.io/)에 대해 설명합니다.

## 무엇인가요?

![textlint lint result](https://user-images.githubusercontent.com/7760903/53157203-58da9580-3604-11e9-88dc-59b96b01fe66.png)

textlint는 텍스트와 마크다운을 위한 linter이며 JavaScript로 구현되어 있습니다. [ESLint](https://eslint.org/)가 JavaScript에 가지는 역할과 같습니다.

## 특정 문맥에서 비활성화할 수 있나요?

[Filter Rule](https://textlint.github.io/docs/configuring.html#filter-rule) 중 하나인 [textlint-filter-rule-comments](https://github.com/textlint/textlint-filter-rule-comments)를 사용해서 비활성화할 수 있습니다. 미리 추가해놨으니 아래처럼 사용하시면 됩니다.

```md
<!-- textlint-disable -->

주석 사이에 있는 글은 모든 규칙이 비활성화됩니다.

<!-- textlint-enable -->
```

## 새로운 규칙(rule)을 어떻게 만드나요?

[textlint의 공식 문서 Creating Rules](https://textlint.github.io/docs/rule.html)를 숙지하고 다음 과정을 진행해주세요. 모든 코드는 `textlint` 폴더에서 작성됩니다.

- **`rules` 폴더에 1개의 규칙에 1개의 파일 생성**

커맨드 라인의 `--rulesdir` 옵션을 통해 실행되므로 `rules` 폴더 하위에는 규칙과 파일을 대응시켜서 작성해주세요.

- **`tests` 폴더에 테스트 코드 작성**

[`textlint-tester`](https://github.com/textlint/textlint/tree/master/packages/textlint-tester)를 활용해서 작성한 규칙에 대응되는 테스트를 작성해주세요. 올바른 사례와 올바르지 못한 사용 사례를 포함하고 올바르지 못한 사례는 번역자가 빠르게 수정할 수 있도록 `index`를 통해 오류가 발생한 위치를 알맞게 안내하고 있는지 검증해주세요.

아래처럼 실행한다면 모든 규칙 구현에 대한 테스트를 실행할 수 있습니다.

```
$ yarn test:textlint
```

## 주의해야 할 사항이 있나요?

- 모든 글이 번역된 상태가 아니며 번역이 완료되어도 새로운 글은 계속해서 번역이 되어야 하기 때문에 git pre-commit hook에서만 textlint를 실행하며 전체 마크다운 파일을 대상으로 CI에서 실행할 계획은 없습니다. 규칙의 구현에 대한 테스트는 CI에서 실행됩니다.
- `--fix`를 통해 자동으로 수정할 수 있는 [Fixable Rule](https://textlint.github.io/docs/rule-fixable.html)은 의도적으로 작성하지 않습니다. 사람이 코드로 작성한 규칙이기 때문에 완벽하지 않으며 번역자가 인지하지 못한 채로 수정되기보다 문맥을 확인하고 수정하는 방향이 바람직하다고 생각하기 때문입니다.
21 changes: 21 additions & 0 deletions textlint/rules/no-endline-colon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 문장 끝 쌍점(:)과 쌍반점(;)에 대한 규칙
*/
module.exports = function(context) {
const {Syntax} = context;
return {
[Syntax.Str](node) {
const {getSource, RuleError, report} = context;
const text = getSource(node);
const match = text.match(/[:;]$/);
if (match) {
report(
node,
new RuleError('문장 끝에 쌍점(:)과 쌍반점(;)은 사용하지 않습니다', {
index: match.index,
}),
);
}
},
};
};
54 changes: 54 additions & 0 deletions textlint/rules/terminology.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 외래어 표기 및 약속된 용어를 위한 규칙
*/
module.exports = function(context) {
const {Syntax} = context;
return {
[Syntax.Str](node) {
const {getSource, RuleError, report} = context;
const text = getSource(node);

for (const term of terms) {
for (const expression of term.expressions) {
let result;
while ((result = expression.exec(text))) {
report(
node,
new RuleError(term.message, {
index: result.index,
}),
);
}
}
}
},
};
};

/**
* 전역 검색을 위한 일련의 정규표현식을 생성합니다.
* @param {RegExp[]} args
* @return {RegExp[]} 'g' 플래그가 설정된 일련의 정규표현식
*/
const g = args => args.map(arg => new RegExp(arg, 'g'));

/**
* @typedef {Object} Terminology
* @property {string} value - 올바른 용어
* @property {RegExp[]} expressions - 올바르지 못한 용어에 대한 일련의 정규표현식
* @property {string} message - 에러 메시지
*
* @type {Terminology[]}
*/
const terms = [
{
value: '메서드',
expressions: [/메소드/, /메쏘드/],
message: 'method는 메서드가 올바른 표현입니다',
},
{
value: '서드파티',
expressions: [/써드파티/],
message: 'third party는 서드파티가 올바른 표현입니다',
},
].map(term => ({...term, expressions: g(term.expressions)}));
22 changes: 22 additions & 0 deletions textlint/tests/no-endline-colon.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const TextLintTester = require('textlint-tester');
const rule = require('../rules/no-endline-colon');

const tester = new TextLintTester();

tester.run('no-endline-colon', rule, {
valid: ['아래와 같습니다.', '제목: 설명입니다.'],
invalid: [
{
text: '아래와 같습니다:',
errors: [{index: 8}],
},
{
text: '아래와 같습니다;',
errors: [{index: 8}],
},
{
text: '여러 줄일 때\n아래와 같습니다:\n',
errors: [{index: 16}],
},
],
});
18 changes: 18 additions & 0 deletions textlint/tests/terminology.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const TextLintTester = require('textlint-tester');
const rule = require('../rules/terminology');

const tester = new TextLintTester();

tester.run('terminology', rule, {
valid: ['메서드', '서드파티'],
invalid: [
{
text: '한 문장에 연속하는 용어 메소드와 메소드와 메쏘드를 테스트합니다.',
errors: [{index: 14}, {index: 19}, {index: 24}],
},
{
text: '써드파티',
errors: [{index: 0}],
},
],
});
Loading