Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
168 changes: 168 additions & 0 deletions gen-translation.go
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)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Path traversal via unsanitized lang argument

lang is used directly in filepath.Join("locales", lang, ...) for both os.MkdirAll and os.WriteFile. While filepath.Join cleans the path, a value like ../../etc resolves outside locales/. Even for a //go:build ignore tool, validating the argument is a low-cost safeguard:

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)
}

The same concern applies to directory values passed explicitly via os.Args[2:] — each entry should be checked to contain no path separators.

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 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

os.Stat error is not distinguished from "file not found"

If os.Stat fails for a reason other than the file not existing (e.g. a permissions issue), the code silently proceeds to write a new file. Prefer:

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
}
13 changes: 13 additions & 0 deletions locales/en.json
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": {}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

en.json is missing the titles object present in all other locale files

zh.json, ja.json, and ko.json all include a "titles" map of English tutorial names → localized names. en.json omits it entirely, making it structurally inconsistent with the other locale files. Since en.json is the canonical schema reference for contributors adding new locales, the missing key is likely to cause incomplete locale files in the future.

Consider adding identity mappings (English → English) or at minimum a comment/schema file in locales/ documenting the expected structure.

60 changes: 60 additions & 0 deletions locales/ja.json
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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The title translation for "Hello world" is missing the Japanese part, unlike other entries which follow the bilingual format (e.g., "Values 値").

Suggested change
"Hello world": "Hello World",
"Hello world": "Hello World ハローワールド",

"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 型スイッチ"
}
}
5 changes: 5 additions & 0 deletions locales/ja/101-Hello-world/hello-11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
XGo の Hello World には3つの書き方があります。
---
### 第1の方法:コマンドスタイル
---
これは私たちが推奨する書き方で、非常に理解しやすいです。特に小中学生にとって、コマンドは最も理解しやすいロジックであり、関数呼び出しよりもはるかに簡単です。
1 change: 1 addition & 0 deletions locales/ja/101-Hello-world/hello-12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
コマンドスタイルへの好みを強調するために、`println` のエイリアスとして `echo` を導入しました:
3 changes: 3 additions & 0 deletions locales/ja/101-Hello-world/hello-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 第2の方法:関数呼び出しスタイル
---
この書き方は Python に似ています。この書き方を理解するための基礎は、関数呼び出しとは何かを理解することです。これはそれほど難しくありません。特に中学生が数学の授業で関数(sin など)を学んでいれば、比較的理解しやすいでしょう。ほとんどのプログラミング言語もこの標準的な関数呼び出し構文をサポートしています。
13 changes: 13 additions & 0 deletions locales/ja/101-Hello-world/hello-3.md
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 をローカルにインストールする方法については、後ほど説明します。
9 changes: 9 additions & 0 deletions locales/ja/102-Values/values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
XGo には文字列、整数、浮動小数点数、ブール値など、さまざまな値の型があります。
---
以下はいくつかの基本的な例です。
---
文字列は `+` で連結できます。
---
整数と浮動小数点数。
---
ブール値と、一般的なブール演算子。
11 changes: 11 additions & 0 deletions locales/ja/103-Constants/constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
XGo は文字、文字列、ブール値、および数値型の_定数_をサポートしています。
---
`const` は定数値を宣言するために使用されます。
---
`const` 文は `var` 文が使える場所ならどこでも使えます。
---
定数式は任意精度の算術演算を行うことができます。
---
数値定数は、明示的な変換などによって型が与えられるまで、型を持ちません。
---
数値は、変数への代入や関数呼び出しなど、特定の型を必要とするコンテキストで使用することで型を得ることができます。例えば、ここでは `math.sin` が `float64` 型の引数を期待しています。
11 changes: 11 additions & 0 deletions locales/ja/104-Variables/vars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
XGo では、_変数_ は明示的に宣言され、コンパイラはそれらを使って関数呼び出しの型の正しさなどをチェックします。
---
`var` は1つ以上の変数を宣言するために使用されます。
---
複数の変数を一度に宣言することができます。
---
XGo は初期化された変数の型を自動的に推論します。
---
初期化なしで宣言された変数には、_ゼロ値_ が設定されます。例えば、`int` のゼロ値は `0` です。
---
`:=` 構文は変数の宣言と初期化を同時に行う省略記法です。例えばこの場合、`var f string = "apple"` と同等です。
3 changes: 3 additions & 0 deletions locales/ja/105-Assignments/assign-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### 代入
---
`=` は代入に使用されます。1行で複数の変数の値を変更できます。この方法により、中間変数を使わずに値を交換することができます。
3 changes: 3 additions & 0 deletions locales/ja/105-Assignments/assign-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### `=` と `:=`
---
`:=` は変数の宣言と初期化に使用されます。1行で複数の変数を宣言して初期化することができます。
7 changes: 7 additions & 0 deletions locales/ja/106-Types/types-2.md
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 です。
19 changes: 19 additions & 0 deletions locales/ja/106-Types/types.md
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 を使用すべきです。
8 changes: 8 additions & 0 deletions locales/ja/107-Integers/integers.md
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 から数値型への変換もサポートしています。以下の例をご覧ください。
8 changes: 8 additions & 0 deletions locales/ja/108-Floating&#45;Point-Numbers/numbers.md
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(非数)が返されます。
3 changes: 3 additions & 0 deletions locales/ja/109-Complex-Numbers/complex-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### 複素数の初期化
XGo には complex64 と complex128 の2つの複素数型があります。
複素数の初期化はとても簡単です。コンストラクタまたは初期化構文を使用できます。
2 changes: 2 additions & 0 deletions locales/ja/109-Complex-Numbers/complex-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### 複素数の構成要素
複素数には実部と虚部の2つの部分があります。それらを取得するために関数を使用します。
4 changes: 4 additions & 0 deletions locales/ja/109-Complex-Numbers/complex-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### 複素数の演算
複素数変数は加算、減算、乗算、除算など、あらゆる演算を行うことができます。

複素数に対する数学演算を実行する例を見てみましょう。
1 change: 1 addition & 0 deletions locales/ja/110-Booleans/boolean-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bool 型はブール変数を表します。bool 型の変数は true または false の2つの値のいずれかしか持つことができません。bool のゼロ値は false です。
1 change: 1 addition & 0 deletions locales/ja/110-Booleans/boolean-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
XGo では、bool を数値型に変換することができます。
21 changes: 21 additions & 0 deletions locales/ja/111-Strings/strings.md
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 はそれもサポートしています。従来の "" では行をまたぐことができませんが、バッククォートを使えば複数行の文字列を定義できます:`
---
バッククォート間のコードはエディタによって特殊文字として認識されず、文字列の一部としてのみ扱われます。
Loading
Loading