-
Notifications
You must be signed in to change notification settings - Fork 19
feat(i18n): add internationalization support for Chinese, Japanese, and Korean #87
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
base: main
Are you sure you want to change the base?
Changes from all commits
14792d3
6a13961
24f8c9c
3c737bc
5eaeb71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| //go:build ignore | ||
|
|
||
| // gen-translation generates skeleton .md translation files for a given language. | ||
| // | ||
| // Usage: | ||
| // | ||
| // go run gen-translation.go <lang> [tutorial-dir...] | ||
| // | ||
| // Examples: | ||
| // | ||
| // go run gen-translation.go zh # generate for all tutorials | ||
| // go run gen-translation.go zh 101-Hello-world # generate for specific tutorial | ||
| // go run gen-translation.go ja 101-Hello-world 102-Values | ||
| // | ||
| // The generated .md files contain the original English doc segments as placeholders, | ||
| // separated by "---". Translators replace the English text with the target language. | ||
| // If a .md file already exists, it will NOT be overwritten. | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
| ) | ||
|
|
||
| const chNumLen = 3 | ||
|
|
||
| func main() { | ||
| if len(os.Args) < 2 { | ||
| fmt.Fprintln(os.Stderr, "Usage: go run gen-translation.go <lang> [tutorial-dir...]") | ||
| os.Exit(1) | ||
| } | ||
| lang := os.Args[1] | ||
| if !regexp.MustCompile(`^[a-z]{2,10}$`).MatchString(lang) { | ||
| fmt.Fprintln(os.Stderr, "Error: lang must be a simple language code (e.g. zh, ja, ko)") | ||
| os.Exit(1) | ||
| } | ||
|
|
||
| var dirs []string | ||
| if len(os.Args) > 2 { | ||
| dirs = os.Args[2:] | ||
| } else { | ||
| // Find all tutorial directories | ||
| fis, err := os.ReadDir(".") | ||
| if err != nil { | ||
| fmt.Fprintln(os.Stderr, "Error reading current directory:", err) | ||
| os.Exit(1) | ||
| } | ||
| for _, fi := range fis { | ||
| if fi.IsDir() { | ||
| name := fi.Name() | ||
| if len(name) > (chNumLen+1) && name[chNumLen] == '-' { | ||
| if _, e := strconv.Atoi(name[:chNumLen]); e == nil { | ||
| // Skip chapter headings (e.g. 100-, 200-) | ||
| if !strings.HasSuffix(name[:chNumLen], "00") { | ||
| dirs = append(dirs, name) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| total := 0 | ||
| skipped := 0 | ||
| for _, dir := range dirs { | ||
| fis, err := os.ReadDir(dir) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Warning: cannot read %s: %v\n", dir, err) | ||
| continue | ||
| } | ||
| langDir := filepath.Join("locales", lang, dir) | ||
| dirCreated := false | ||
| for _, fi := range fis { | ||
| fname := fi.Name() | ||
| ext := filepath.Ext(fname) | ||
| if ext != ".gop" && ext != ".xgo" { | ||
| continue | ||
| } | ||
|
|
||
| mdName := strings.TrimSuffix(fname, ext) + ".md" | ||
| mdPath := filepath.Join("locales", lang, dir, mdName) | ||
|
|
||
| // Skip if already exists | ||
| if _, err := os.Stat(mdPath); err == nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If if _, err := os.Stat(mdPath); err == nil {
skipped++
continue
} else if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Warning: cannot stat %s: %v\n", mdPath, err)
continue
} |
||
| skipped++ | ||
| continue | ||
| } else if !os.IsNotExist(err) { | ||
| fmt.Fprintf(os.Stderr, "Warning: cannot stat %s: %v\n", mdPath, err) | ||
| continue | ||
| } | ||
|
|
||
| // Parse .gop to extract doc segments | ||
| content, err := os.ReadFile(filepath.Join(dir, fname)) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Warning: cannot read %s: %v\n", fname, err) | ||
| continue | ||
| } | ||
|
|
||
| docs := extractDocSegments(string(content)) | ||
| if len(docs) == 0 { | ||
| continue | ||
| } | ||
|
|
||
| // Create lang directory once per tutorial dir | ||
| if !dirCreated { | ||
| if err := os.MkdirAll(langDir, 0755); err != nil { | ||
| fmt.Fprintf(os.Stderr, "Error creating %s: %v\n", langDir, err) | ||
| break | ||
| } | ||
| dirCreated = true | ||
| } | ||
|
|
||
| // Write skeleton .md | ||
| skeleton := strings.Join(docs, "\n\n---\n\n") | ||
| if err := os.WriteFile(mdPath, []byte(skeleton+"\n"), 0644); err != nil { | ||
| fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", mdPath, err) | ||
| continue | ||
| } | ||
|
|
||
| fmt.Printf("Created: %s (%d segments)\n", mdPath, len(docs)) | ||
| total++ | ||
| } | ||
| } | ||
| fmt.Printf("\nDone: %d files created, %d skipped (already exist)\n", total, skipped) | ||
| } | ||
|
|
||
| // extractDocSegments parses a .gop file and returns the text of each doc segment. | ||
| func extractDocSegments(content string) []string { | ||
| lines := strings.Split(content, "\n") | ||
| var segments []string | ||
| var current []string | ||
| inDoc := false | ||
|
|
||
| for _, line := range lines { | ||
| trimmed := strings.TrimSpace(line) | ||
| isDoc := false | ||
| var docText string | ||
|
|
||
| if strings.HasPrefix(trimmed, "//") { | ||
| isDoc = true | ||
| docText = strings.TrimPrefix(trimmed[2:], " ") | ||
| } else if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "#!") { | ||
| isDoc = true | ||
| docText = "## " + strings.TrimPrefix(strings.TrimPrefix(trimmed, "#"), " ") | ||
| } | ||
|
|
||
| if isDoc { | ||
| current = append(current, docText) | ||
| inDoc = true | ||
| } else { | ||
| if inDoc && len(current) > 0 { | ||
| segments = append(segments, strings.Join(current, "\n")) | ||
| current = nil | ||
| } | ||
| inDoc = false | ||
| } | ||
| } | ||
| // Flush last segment | ||
| if len(current) > 0 { | ||
| segments = append(segments, strings.Join(current, "\n")) | ||
| } | ||
|
|
||
| return segments | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "site_title": "XGo by Tutorials", | ||
| "breadcrumb_tutorials": "Tutorials", | ||
| "index_heading": "Tutorials", | ||
| "index_desc_1": "<a href=\"https://xgo.dev\">XGo</a> is an open source programming language aimed to enable everyone to become a builder of the world.", | ||
| "index_desc_2": "<em>XGo by Tutorials</em> is a hands-on introduction to XGo using annotated example programs. Check out the <a href=\"hello-world\">first example</a> or browse the full list below.", | ||
| "no_content_before": "No content yet, you can help us build it ", | ||
| "no_content_link": "here", | ||
| "no_content_after": ".", | ||
| "next_example": "Next example:", | ||
| "lang_switcher_label": "Switch language", | ||
| "titles": {} | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider adding identity mappings (English → English) or at minimum a comment/schema file in |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| { | ||
| "site_title": "XGo チュートリアル", | ||
| "breadcrumb_tutorials": "チュートリアル", | ||
| "index_heading": "チュートリアル", | ||
| "index_desc_1": "<a href=\"https://xgo.dev\">XGo</a> は、すべての人が世界の構築者になれることを目指したオープンソースプログラミング言語です。", | ||
| "index_desc_2": "<em>XGo チュートリアル</em>は、注釈付きのサンプルプログラムを通じて XGo を学ぶ実践的なチュートリアルです。<a href=\"hello-world\">最初の例</a>をご覧になるか、以下の一覧からお選びください。", | ||
| "no_content_before": "まだコンテンツがありません。", | ||
| "no_content_link": "こちら", | ||
| "no_content_after": "で作成にご協力ください。", | ||
| "next_example": "次の例:", | ||
| "lang_switcher_label": "言語を切り替える", | ||
| "titles": { | ||
| "Sequential programming": "Sequential Programming 順次プログラミング", | ||
| "Structured programming": "Structured Programming 構造化プログラミング", | ||
| "Hello world": "Hello World", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| "Values": "Values 値", | ||
| "Constants": "Constants 定数", | ||
| "Variables": "Variables 変数", | ||
| "Assignments": "Assignments 代入", | ||
| "Types": "Types 型", | ||
| "Integers": "Integers 整数", | ||
| "Floating-Point Numbers": "Floating-Point Numbers 浮動小数点数", | ||
| "Complex Numbers": "Complex Numbers 複素数", | ||
| "Booleans": "Booleans ブーリアン", | ||
| "Strings": "Strings 文字列", | ||
| "Rational Numbers": "Rational Numbers 有理数", | ||
| "If/Else": "If/Else 条件分岐", | ||
| "Switch": "Switch 分岐", | ||
| "For": "For ループ", | ||
| "Arrays": "Arrays 配列", | ||
| "Slices": "Slices スライス", | ||
| "Maps": "Maps マップ", | ||
| "Structs": "Structs 構造体", | ||
| "Pointers": "Pointers ポインタ", | ||
| "For Each": "For Each 各要素の走査", | ||
| "For Range": "For Range range 節", | ||
| "List Comprehension": "List Comprehension リスト内包表記", | ||
| "Functions": "Functions 関数", | ||
| "Multiple Return Values": "Multiple Return Values 複数戻り値", | ||
| "Errors": "Errors エラー処理", | ||
| "Function Values": "Function Values 関数値", | ||
| "Closures": "Closures クロージャ", | ||
| "Lambda expressions": "Lambda Expressions ラムダ式", | ||
| "Recursion": "Recursion 再帰", | ||
| "Variadic Parameters": "Variadic Parameters 可変長引数", | ||
| "Defer": "Defer 遅延実行", | ||
| "Exceptions": "Exceptions 例外処理", | ||
| "Methods": "Methods メソッド", | ||
| "Methods with a Pointer Receiver": "Methods with a Pointer Receiver ポインタレシーバのメソッド", | ||
| "Composing Types by Struct Embedding": "Composing Types by Struct Embedding 構造体埋め込みによる型合成", | ||
| "Method Values and Expressions": "Method Values and Expressions メソッド値とメソッド式", | ||
| "Encapsulation": "Encapsulation カプセル化", | ||
| "Interfaces": "Interfaces インターフェース", | ||
| "Interface Satisfaction": "Interface Satisfaction インターフェース実装", | ||
| "Interface Values": "Interface Values インターフェース値", | ||
| "The error Interface": "The error Interface エラーインターフェース", | ||
| "Type Assertions": "Type Assertions 型アサーション", | ||
| "Type Switches": "Type Switches 型スイッチ" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| XGo の Hello World には3つの書き方があります。 | ||
| --- | ||
| ### 第1の方法:コマンドスタイル | ||
| --- | ||
| これは私たちが推奨する書き方で、非常に理解しやすいです。特に小中学生にとって、コマンドは最も理解しやすいロジックであり、関数呼び出しよりもはるかに簡単です。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| コマンドスタイルへの好みを強調するために、`println` のエイリアスとして `echo` を導入しました: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ## 第2の方法:関数呼び出しスタイル | ||
| --- | ||
| この書き方は Python に似ています。この書き方を理解するための基礎は、関数呼び出しとは何かを理解することです。これはそれほど難しくありません。特に中学生が数学の授業で関数(sin など)を学んでいれば、比較的理解しやすいでしょう。ほとんどのプログラミング言語もこの標準的な関数呼び出し構文をサポートしています。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| ## 第3の方法:ソフトウェアエンジニアリングスタイル | ||
| --- | ||
| これは Go 言語から継承された標準的なソフトウェアエンジニアリングの書き方です。初心者には理解しにくいかもしれません。なぜなら、関数(func)とは何か、パッケージ(package)とは何かを理解する必要があるからです。もちろん、機能分解やチームコラボレーションの基本的なロジックを構築し始められるという利点もあります。 | ||
| --- | ||
| では、XGo を体験するにはどうすればよいでしょうか? | ||
| --- | ||
| 最も簡単な方法は、XGo Playground に直接アクセスして体験することです: | ||
| --- | ||
| * https://play.xgo.dev | ||
| --- | ||
| 基本的な文法を学んでいる初期段階では、この方法で十分です。 | ||
| --- | ||
| XGo をローカルにインストールする方法については、後ほど説明します。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| XGo には文字列、整数、浮動小数点数、ブール値など、さまざまな値の型があります。 | ||
| --- | ||
| 以下はいくつかの基本的な例です。 | ||
| --- | ||
| 文字列は `+` で連結できます。 | ||
| --- | ||
| 整数と浮動小数点数。 | ||
| --- | ||
| ブール値と、一般的なブール演算子。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| XGo は文字、文字列、ブール値、および数値型の_定数_をサポートしています。 | ||
| --- | ||
| `const` は定数値を宣言するために使用されます。 | ||
| --- | ||
| `const` 文は `var` 文が使える場所ならどこでも使えます。 | ||
| --- | ||
| 定数式は任意精度の算術演算を行うことができます。 | ||
| --- | ||
| 数値定数は、明示的な変換などによって型が与えられるまで、型を持ちません。 | ||
| --- | ||
| 数値は、変数への代入や関数呼び出しなど、特定の型を必要とするコンテキストで使用することで型を得ることができます。例えば、ここでは `math.sin` が `float64` 型の引数を期待しています。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| XGo では、_変数_ は明示的に宣言され、コンパイラはそれらを使って関数呼び出しの型の正しさなどをチェックします。 | ||
| --- | ||
| `var` は1つ以上の変数を宣言するために使用されます。 | ||
| --- | ||
| 複数の変数を一度に宣言することができます。 | ||
| --- | ||
| XGo は初期化された変数の型を自動的に推論します。 | ||
| --- | ||
| 初期化なしで宣言された変数には、_ゼロ値_ が設定されます。例えば、`int` のゼロ値は `0` です。 | ||
| --- | ||
| `:=` 構文は変数の宣言と初期化を同時に行う省略記法です。例えばこの場合、`var f string = "apple"` と同等です。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### 代入 | ||
| --- | ||
| `=` は代入に使用されます。1行で複数の変数の値を変更できます。この方法により、中間変数を使わずに値を交換することができます。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### `=` と `:=` | ||
| --- | ||
| `:=` は変数の宣言と初期化に使用されます。1行で複数の変数を宣言して初期化することができます。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ### XGo は有理数のビルトインサポートを備えています: | ||
|
|
||
|
|
||
| XGo の基本型として有理数を導入しました。有理数リテラルを示すためにサフィックス `r` を使用します。例えば、`1r << 200` は 2^200 に等しい値を持つ大きな整数を意味します。 | ||
|
|
||
| デフォルトでは、`1r` の型は bigint です。 | ||
| また、`4/5r` は有理定数 4/5 を表し、その型は bigrat です。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| ### XGo の基本型 | ||
| <pre> | ||
| bool | ||
| int8 int16 int32 int int64 int128 | ||
| uint8 uint16 uint32 uint uint64 uint128 | ||
| uintptr(C 言語の size_t に類似) | ||
| byte(uint8 のエイリアス) | ||
| rune(int32 のエイリアス、Unicode コードポイントを表す) | ||
| string | ||
| float32 float64 | ||
| complex64 complex128 | ||
| bigint bigrat | ||
| unsafe.Pointer(C 言語の void* に類似) | ||
| any(Go の interface{} のエイリアス) | ||
| </pre> | ||
|
|
||
| この例では複数の型の変数を示すとともに、変数宣言が import 文と同様にブロックとして「グループ化」できることも示しています。 | ||
| --- | ||
| int、uint、uintptr 型は通常、32ビットシステムでは32ビット幅、64ビットシステムでは64ビット幅です。整数値が必要な場合は、固定サイズまたは符号なし整数型を使用する特定の理由がない限り、int を使用すべきです。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| int 型は整数を表し、正の数にも負の数にもなります。int 型のサイズはプラットフォームに依存し、32ビットまたは64ビットのいずれかです。int8、int16、int32、int64、int128 など特定のサイズを持つ整数型もありますが、特定のサイズが必要でない限り int 型を使用すべきです。 | ||
|
|
||
| uint 型は正の整数を表します。uint 型のサイズはプラットフォームに依存し、32ビットまたは64ビットのいずれかです。uint8、uint16、uint32、uint64、uint128 など特定のサイズを持つ符号なし整数型もありますが、特定のサイズが必要でない限り uint 型を使用すべきです。 | ||
|
|
||
| int の値 20 は、16進数 (0x14)、8進数 (0o24)、2進数 (0b0010100) でも表現できます。 | ||
| uint には uint リテラルがなく、すべての整数リテラルは int 値として扱われます。 | ||
|
|
||
| XGo は数値の区切り文字として `_` の使用もサポートしており、bool から数値型への変換もサポートしています。以下の例をご覧ください。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| XGo には float32 と float64 の2つの浮動小数点型があります。 | ||
| 浮動小数点リテラルのデフォルトの型は float64 です。 | ||
|
|
||
| 浮動小数点数は10進数の値を正確に表現できません。通貨やその他正確な10進数表現が必要な値には使用しないでください! | ||
|
|
||
| == や != を使って浮動小数点数を比較することはできますが、推奨されません。浮動小数点数の不正確な性質により、等しいはずの2つの浮動小数点値が等しくならない場合があります。代わりに、最小許容偏差を定義し、2つの浮動小数点数の差がその値より小さいかどうかを確認してください。この最小値(イプシロンと呼ばれることもあります)は、精度の要件によって異なります。 | ||
|
|
||
| 浮動小数点リテラルは10の累乗でも宣言でき、値が0の浮動小数点変数を0で除算すると NaN(非数)が返されます。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### 複素数の初期化 | ||
| XGo には complex64 と complex128 の2つの複素数型があります。 | ||
| 複素数の初期化はとても簡単です。コンストラクタまたは初期化構文を使用できます。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| ### 複素数の構成要素 | ||
| 複素数には実部と虚部の2つの部分があります。それらを取得するために関数を使用します。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ### 複素数の演算 | ||
| 複素数変数は加算、減算、乗算、除算など、あらゆる演算を行うことができます。 | ||
|
|
||
| 複素数に対する数学演算を実行する例を見てみましょう。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| bool 型はブール変数を表します。bool 型の変数は true または false の2つの値のいずれかしか持つことができません。bool のゼロ値は false です。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| XGo では、bool を数値型に変換することができます。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| 文字列は変更できないバイトのシーケンスです。文字列は任意のデータを含むことができ、通常はテキストを保持するために使用されます。 | ||
|
|
||
| 一般的にダブルクォート "" を使って文字列を定義しますが、特定の文字には特別な意味があることに注意してください。これらはエスケープ文字と呼ばれ、以下のものがあります: | ||
|
|
||
| <pre style="font-size:small"> | ||
| \n:改行 | ||
| \r:復帰 | ||
| \t:タブ | ||
| \u または \U:Unicode | ||
| \:バックスラッシュ | ||
| </pre> | ||
| --- | ||
| 文字列が占めるバイト数を知りたい場合は、XGo の組み込み関数を使って計算できます: | ||
| --- | ||
| 文字列を定義する場合、構文は以下の通りです: | ||
| --- | ||
| + を使って2つの文字列を連結し、後の文字列を前の文字列の末尾に追加できます。 | ||
| --- | ||
| 複数行の文字列を定義したい場合、XGo はそれもサポートしています。従来の "" では行をまたぐことができませんが、バッククォートを使えば複数行の文字列を定義できます:` | ||
| --- | ||
| バッククォート間のコードはエディタによって特殊文字として認識されず、文字列の一部としてのみ扱われます。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path traversal via unsanitized
langargumentlangis used directly infilepath.Join("locales", lang, ...)for bothos.MkdirAllandos.WriteFile. Whilefilepath.Joincleans the path, a value like../../etcresolves outsidelocales/. Even for a//go:build ignoretool, validating the argument is a low-cost safeguard:The same concern applies to directory values passed explicitly via
os.Args[2:]— each entry should be checked to contain no path separators.