Skip to content

Commit b60c96c

Browse files
committed
feat: implement language file loading
1. state.State implements Stringer, TextMarshaler and TextUnmarshaler 2. language can be loaded from translation file 3. add sample translation 4. Makefile and README
1 parent e57b6d5 commit b60c96c

File tree

9 files changed

+226
-26
lines changed

9 files changed

+226
-26
lines changed

Makefile

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
APPNAME := commit-msg
22
LDFLAGS += -s -w
33

4-
.PHONY: all build upx
4+
.PHONY: all gen build upx
55

6-
all: build upx
6+
all: gen build upx
7+
8+
gen:
9+
go generate ./...
710

811
build:
912
go build -trimpath -ldflags "$(LDFLAGS) -X 'main.version=$(TAG)' -X 'main.goVersion=$(shell go version)' -X 'main.commitHash=$(shell git show -s --format=%H)' -X 'main.buildTime=$(shell date "+%Y-%m-%d %T%z")'"

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,30 @@ If there are no configuration files, the program will use the following default
101101
}
102102
```
103103

104+
## Localization
105+
106+
The program has built-in two languages: English (en) and Chinese (zh).
107+
108+
You can add language support in two ways:
109+
110+
### Contributing code
111+
112+
The built-in languages are placed in the `lang` directory, with individual `.go` source file for each. You can copy `lang/english.go`, change the file name and translate the content, then commit it to launch a Pull Request. The language will be added in next release after the PR is approved.
113+
114+
Of course, considering my limited English (my Chinese wording is not necessarily good either), you can also help improve the existing language support.
115+
116+
### Add translation file
117+
118+
However, committing code may not be the best way to go.
119+
120+
Firstly , committing code is time-consuming. If you can't compile it yourself, but have to wait for next version release after the code merged in, it would be a long time until the version is ready. On the other hand, this is only a small tool after all, and to avoid the program becoming bloated, some languages may not be merged in, depending on the number of users.
121+
122+
You can choose a quicker way: adding translation file.
123+
124+
To do this, copy the [commit-msg.en.json.sample](./commit-msg.en.json.sample) file in the project root directory, remove `.sample` from the file name, and change `en` to the corresponding language (e.g., the abbreviation for language to be supported is `xx`) . Translate the contents of the file, keeping the formatting verb `%s` and the line break `\n`. Then put the file in the same directory as the configuration file (`home` directory or `hooks` directory). Afterwards, remember to change the language configuration to the corresponding language (`xx` in this case).
125+
126+
Unlike the configuration file, if the translations for the same language exist in both `home` directory and `hooks` directory, the contents of both will not be merged, but the translation in `hooks` directory of the project will prevail. The contents of each language file must be a complete translation.
127+
104128
## More info
105129

106130
The project was first inspired by [validate-commit-msg](https://github.com/conventional-changelog-archived-repos/validate-commit-msg) . ( It has now been moved to [conventional-changelog/commitlint](https://github.com/conventional-changelog/commitlint) )

README_zh.md

+24
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,30 @@ the way to migrate:
101101
}
102102
```
103103

104+
## 本地化
105+
106+
程序内置了两种语言的提示:英语(en) 和 中文(zh)。
107+
108+
你可以通过以下两种方式增加支持的语言:
109+
110+
### 提交代码
111+
112+
内置的语言支持放在 `lang` 目录下,每种语言一个 `.go` 源文件。你可以拷贝 `lang/english.go` ,修改文件名并翻译内容,然后提交,发起 Pull Request。待 PR 通过后,下一个版本就可以支持对应的语言。
113+
114+
当然,考虑到我的英语水平有限(中文的措辞也不见得很好),你也可以帮忙改进已有的语言支持。
115+
116+
### 增加语言翻译文件
117+
118+
但提交代码未必是最好的办法。
119+
120+
一方面,提交代码耗时较多。如果你不能自行编译,而是要等代码合入之后发布下一个版本,还需要等待更多的时间。另一方面,这毕竟只是一个小工具,为了避免程序变得臃肿,视乎使用者的多寡,一些语言我可能不会选择合入。
121+
122+
你可以选择更快捷的方式:增加语言文件。
123+
124+
具体的做法是,拷贝项目根目录下的 [commit-msg.en.json.sample](./commit-msg.en.json.sample) 文件,去掉文件名里的 `.sample` ,把 `en` 改为对应的语言(举例说这种语言的缩写为 `xx`)。把文件内容翻译好,注意保留里面的格式化动词 `%s` 和换行符 `\n` 。然后把文件放到跟配置文件相同的目录(`home` 目录或者 `hooks` 目录)。之后记得修改语言配置为对应的语言(这里是 `xx`)。
125+
126+
跟配置文件不同,如果相同语言的翻译在 `home` 目录和 `hooks` 目录同时存在,并不会合并两者的内容,而是直接以项目 `hooks` 目录的翻译为准。所以每一个语言文件里的内容,都必须是完整的翻译。
127+
104128
## 更多信息
105129

106130
本项目最早受 [validate-commit-msg](https://github.com/conventional-changelog-archived-repos/validate-commit-msg) 启发。(该项目现已移至 [conventional-changelog/commitlint](https://github.com/conventional-changelog/commitlint)

commit-msg.en.json.sample

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"hints":
3+
{
4+
"Validated": "Validated: commit message meet the rule.",
5+
"Merge": "Merge: merge commit detected,skip check.",
6+
"ArgumentMissing": "Error ArgumentMissing: commit message file argument missing.",
7+
"FileMissing": "Error FileMissing: file %s not exists.",
8+
"ReadError": "Error ReadError: read file %s error.",
9+
"EmptyMessage": "Error EmptyMessage: commit message has no content except whitespaces.",
10+
"EmptyHeader": "Error EmptyHeader: header (first line) has no content except whitespaces.",
11+
"BadHeaderFormat": "Error BadHeaderFormat: header (first line) not following the rule:\n%s\nif you can not find any error after check, maybe you use full-width colon, or lack of whitespace after the colon.",
12+
"WrongType": "Error WrongType: %s, type should be one of the keywords:\n%s",
13+
"ScopeMissing": "Error ScopeMissing: (scope) is required right after type.",
14+
"WrongScope": "Error WrongScope: %s, scope should be one of the keywords:\n%s",
15+
"BodyMissing": "Error BodyMissing: body has no content except whitespaces.",
16+
"NoBlankLineBeforeBody": "Error NoBlankLineBeforeBody: no empty line between header and body.",
17+
"LineOverLong": "Error LineOverLong: the length of line is %d, exceed %d:\n%s",
18+
"UndefindedError": "Error UndefindedError: unexpected error occurs, please raise an issue."
19+
},
20+
"rule": "Commit message rule as follow:\n<type>(<scope>): <subject>\n// empty line\n<body>\n// empty line\n<footer>\n\n(<scope>), <body> and <footer> are optional by default\n<type> must be one of %s\nmore specific instructions, please refer to: https://github.com/JayceChant/commit-msg"
21+
}

dir/dir.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dir
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
7+
"github.com/mitchellh/go-homedir"
8+
)
9+
10+
const (
11+
hookDir = "./.git/hooks/"
12+
)
13+
14+
func FindFiles(file string) []string {
15+
paths := make([]string, 0)
16+
if home, err := homedir.Dir(); err == nil {
17+
paths = append(paths, filepath.Join(home, file))
18+
}
19+
20+
f, err := os.Stat(hookDir)
21+
if (err == nil || os.IsExist(err)) && f.IsDir() {
22+
// working dir is on project root
23+
paths = append(paths, filepath.Join(hookDir, file))
24+
} else {
25+
// work around for test
26+
paths = append(paths, file)
27+
}
28+
return paths
29+
}
30+
31+
func FindFirstExist(file string) string {
32+
if isFile(file) {
33+
return file
34+
}
35+
36+
hookFile := filepath.Join(hookDir, file)
37+
if isFile(hookFile) {
38+
return hookFile
39+
}
40+
41+
if home, err := homedir.Dir(); err == nil {
42+
homeFile := filepath.Join(home, file)
43+
if isFile(homeFile) {
44+
return homeFile
45+
}
46+
}
47+
48+
return ""
49+
}
50+
51+
func isFile(path string) bool {
52+
fi, err := os.Stat(path)
53+
if err != nil && !os.IsExist(err) {
54+
return false
55+
}
56+
return !fi.IsDir()
57+
}

lang/lang.go

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,51 @@
11
package lang
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
"log"
7+
"os"
58

9+
"github.com/JayceChant/commit-msg/dir"
610
. "github.com/JayceChant/commit-msg/state"
711
)
812

13+
const (
14+
langFile = "commit-msg.%s.json"
15+
)
16+
917
func LoadLanguage(language string) LangPack {
1018
if l, ok := langs[language]; ok {
1119
return l
1220
}
13-
return langEn
21+
22+
file := fmt.Sprintf(langFile, language)
23+
path := dir.FindFirstExist(file)
24+
if path == "" {
25+
return langEn
26+
}
27+
28+
f, err := os.Open(path)
29+
if err != nil && !os.IsExist(err) {
30+
log.Printf("open language %v error: %v\n", language, err)
31+
return langEn
32+
}
33+
defer f.Close()
34+
35+
dec := json.NewDecoder(f)
36+
l := &langPack{}
37+
err = dec.Decode(l)
38+
if err != nil {
39+
log.Printf("decode language %v error: %v\n", language, err)
40+
return langEn
41+
}
42+
return l
1443
}
1544

1645
// langPack ...
1746
type langPack struct {
18-
Hints map[State]string
19-
Rule string
47+
Hints map[State]string `json:"hints"`
48+
Rule string `json:"rule"`
2049
}
2150

2251
func (l *langPack) GetHint(state State, v ...interface{}) string {

state/state.go

+24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
//go:generate stringer -type=State
12
package state
23

34
import (
5+
"encoding"
6+
"fmt"
47
"log"
58
"os"
69
)
@@ -21,6 +24,12 @@ type LangPack interface {
2124
GetRule(types string) string
2225
}
2326

27+
func _() {
28+
// type check
29+
var _ encoding.TextMarshaler = State(0)
30+
var _ encoding.TextUnmarshaler = (*State)(nil)
31+
}
32+
2433
// State indicate the state of a commit message
2534
type State int8
2635

@@ -70,3 +79,18 @@ func (state State) IsNormal() bool {
7079
func (state State) IsFormatError() bool {
7180
return state >= EmptyMessage
7281
}
82+
83+
func (state State) MarshalText() (text []byte, err error) {
84+
return []byte(state.String()), nil
85+
}
86+
87+
func (state *State) UnmarshalText(text []byte) error {
88+
str := string(text)
89+
for s := Validated; s <= UndefindedError; s++ {
90+
if s.String() == str {
91+
*state = s
92+
return nil
93+
}
94+
}
95+
return fmt.Errorf("unknown state : %v", str)
96+
}

state/state_string.go

+37
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

validator/config.go

+2-21
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ import (
44
"encoding/json"
55
"log"
66
"os"
7-
"path/filepath"
87
"strings"
98

9+
"github.com/JayceChant/commit-msg/dir"
1010
"github.com/JayceChant/commit-msg/lang"
1111
"github.com/JayceChant/commit-msg/state"
12-
homedir "github.com/mitchellh/go-homedir"
1312
)
1413

1514
const (
1615
configFileName = ".commit-msg.json"
17-
hookDir = "./.git/hooks/"
1816
)
1917

2018
type validateConfig struct {
@@ -51,23 +49,6 @@ var (
5149
TypesStr string
5250
)
5351

54-
func locateConfigs() []string {
55-
paths := make([]string, 0)
56-
if home, err := homedir.Dir(); err == nil {
57-
paths = append(paths, filepath.Join(home, configFileName))
58-
}
59-
60-
f, err := os.Stat(hookDir)
61-
if (err == nil || os.IsExist(err)) && f.IsDir() {
62-
// working dir is on project root
63-
paths = append(paths, filepath.Join(hookDir, configFileName))
64-
} else {
65-
// work around for test
66-
paths = append(paths, configFileName)
67-
}
68-
return paths
69-
}
70-
7152
func loadConfig(path string, cfg *validateConfig) *validateConfig {
7253
f, err := os.Open(path)
7354
if err != nil && !os.IsExist(err) {
@@ -83,7 +64,7 @@ func loadConfig(path string, cfg *validateConfig) *validateConfig {
8364
}
8465

8566
func init() {
86-
paths := locateConfigs()
67+
paths := dir.FindFiles(configFileName)
8768
for _, p := range paths {
8869
// TODO: fix json value overlaping
8970
globalConfig = loadConfig(p, globalConfig)

0 commit comments

Comments
 (0)