diff --git a/Cargo.toml b/Cargo.toml index 82bd96bb..ea085632 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,16 @@ japanese = [ "erg_parser/japanese", "erg_compiler/japanese", ] +simplified_chinese = [ + "erg_common/simplified_chinese", + "erg_parser/simplified_chinese", + "erg_compiler/simplified_chinese", +] +traditional_chinese = [ + "erg_common/traditional_chinese", + "erg_parser/traditional_chinese", + "erg_compiler/traditional_chinese", +] [dependencies] erg_common = { version = "0.2.5", path = "./compiler/erg_common" } diff --git a/assets/screenshot_i18n_messages.png b/assets/screenshot_i18n_messages.png new file mode 100644 index 00000000..1936492b Binary files /dev/null and b/assets/screenshot_i18n_messages.png differ diff --git a/compiler/erg_common/Cargo.toml b/compiler/erg_common/Cargo.toml index 418aca0c..45d3dcb1 100644 --- a/compiler/erg_common/Cargo.toml +++ b/compiler/erg_common/Cargo.toml @@ -14,6 +14,8 @@ homepage = "https://erg-lang.github.io/" [features] debug = [] japanese = [] +simplified_chinese = [] +traditional_chinese = [] [dependencies] atty = "0.2.14" diff --git a/compiler/erg_common/deserialize.rs b/compiler/erg_common/deserialize.rs index cb29b67f..e0f8212e 100644 --- a/compiler/erg_common/deserialize.rs +++ b/compiler/erg_common/deserialize.rs @@ -58,8 +58,8 @@ impl DeserializeError { 0, fn_name!(), switch_lang!( - "the loaded .pyc file is broken", - "読み込んだ.pycファイルは破損しています" + "japanese" => "読み込んだ.pycファイルは破損しています", + "english" => "the loaded .pyc file is broken", ), ) } @@ -69,14 +69,14 @@ impl DeserializeError { 0, fn_name!(), switch_lang!( - format!( + "japanese" => format!( + "{}型オブジェクトを予期しましたが、 読み込んだオブジェクトは{}型です", + expect, found + ), + "english" => format!( "expect a {} object, but the deserialized object is {}", expect, found ), - format!( - "{}型オブジェクトを予期しましたが、 読み込んだオブジェクトは{}型です", - expect, found - ) ), ) } @@ -228,8 +228,8 @@ impl Deserializer { 0, fn_name!(), switch_lang!( - format!("cannot deserialize this object: {}", other), - format!("このオブジェクトは復元できません: {}", other) + "japanese" => format!("このオブジェクトは復元できません: {}", other), + "english" => format!("cannot deserialize this object: {}", other), ), )), } @@ -300,7 +300,10 @@ impl Deserializer { return Err(DeserializeError::new( 0, fn_name!(), - switch_lang!("failed to load bytes", "バイト列の読み込みに失敗しました"), + switch_lang!( + "japanese" => "バイト列の読み込みに失敗しました", + "english" => "failed to load bytes", + ), )); } let len = Self::deserialize_u32(v); diff --git a/compiler/erg_common/error.rs b/compiler/erg_common/error.rs index d5cc76bb..af737181 100644 --- a/compiler/erg_common/error.rs +++ b/compiler/erg_common/error.rs @@ -321,10 +321,16 @@ impl ErrorCore { } pub fn bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self { - Self::new(errno, CompilerSystemError, loc, switch_lang!( - format!("this is a bug of Erg, please report it to https://github.com/...\ncaused from: {fn_name}:{line}"), - format!("これはErgのバグです、開発者に報告して下さい (https://github.com/...)\n{fn_name}:{line}より発生") - ), None) + Self::new( + errno, + CompilerSystemError, + loc, + switch_lang!( + "japanese" => format!("これはErgのバグです、開発者に報告して下さい (https://github.com/...)\n{fn_name}:{line}より発生"), + "english" => format!("this is a bug of Erg, please report it to https://github.com/...\ncaused from: {fn_name}:{line}"), + ), + None, + ) } } diff --git a/compiler/erg_common/macros.rs b/compiler/erg_common/macros.rs index bb22604b..592e3949 100644 --- a/compiler/erg_common/macros.rs +++ b/compiler/erg_common/macros.rs @@ -58,11 +58,17 @@ macro_rules! impl_display_for_enum_with_variant { /// マクロはパラメータを展開しないので、format!のロスがなくなる #[macro_export] macro_rules! switch_lang { - ($en: expr, $jp: expr $(,)*) => {{ - if cfg!(feature = "japanese") { - $jp + ( + $should_english: literal => $msg: expr, + ) => {{ $msg }}; + ( + $lang_name: literal => $msg: expr, + $($rest_lang_name: literal => $rest_msg: expr,)+ + ) => {{ + if cfg!(feature = $lang_name) { + $msg } else { - $en + switch_lang!($($rest_lang_name => $rest_msg,)+) } }}; } diff --git a/compiler/erg_common/serialize.rs b/compiler/erg_common/serialize.rs index a4d2717b..af1f517f 100644 --- a/compiler/erg_common/serialize.rs +++ b/compiler/erg_common/serialize.rs @@ -49,14 +49,14 @@ pub enum DataTypePrefix { StopIter = b'S', // 0x53 Ref = b'r', /* unsized objects (ref counted) */ - Long = b'l', // 0x6C + len: u32 + payload: 2*len+3byte (~ -2^31-1 && 2^31 ~) - Str = b's', // 0x73 + len: u32 + payload - ShortAscii = b'z', // 0x7A + len: u8 + payload - ShortAsciiInterned = b'Z', // 0x5A + len: u8 + payload - Unicode = b'u', // 0x75 + len: u32 + payload + Long = b'l', // 0x6C + len: u32 + payload: 2*len+3byte (~ -2^31-1 && 2^31 ~) + Str = b's', // 0x73 + len: u32 + payload + ShortAscii = b'z', // 0x7A + len: u8 + payload + ShortAsciiInterned = b'Z', // 0x5A + len: u8 + payload + Unicode = b'u', // 0x75 + len: u32 + payload Interned = b't', // 0x74 + len + payload - SmallTuple = b')', // 0x29 + len: u8 + payload - Tuple = b'(', // 0x28 + len: u32 + payload + SmallTuple = b')', // 0x29 + len: u8 + payload + Tuple = b'(', // 0x28 + len: u32 + payload Code = b'c', // 0x63 /* Erg specific prefix */ Builtin = b'b', // 0x62 + str diff --git a/compiler/erg_common/value.rs b/compiler/erg_common/value.rs index bcd348d5..d4c7ac2a 100644 --- a/compiler/erg_common/value.rs +++ b/compiler/erg_common/value.rs @@ -326,8 +326,8 @@ impl ValueObj { panic!( "{}", switch_lang!( - format!("this object cannot be serialized: {other}"), - format!("このオブジェクトはシリアライズできません: {other}") + "japanese" => format!("このオブジェクトはシリアライズできません: {other}"), + "english" => format!("this object cannot be serialized: {other}"), ) ) } diff --git a/compiler/erg_compiler/Cargo.toml b/compiler/erg_compiler/Cargo.toml index 9a141d04..7de1b1f3 100644 --- a/compiler/erg_compiler/Cargo.toml +++ b/compiler/erg_compiler/Cargo.toml @@ -13,6 +13,8 @@ homepage = "https://erg-lang.github.io/" # when "debug" feature is turned on, that of parser will also be turned on. debug = [ "erg_common/debug", "erg_parser/debug" ] japanese = [ "erg_common/japanese", "erg_parser/japanese" ] +simplified_chinese = [ "erg_common/simplified_chinese", "erg_parser/simplified_chinese" ] +traditional_chinese = [ "erg_common/traditional_chinese", "erg_parser/traditional_chinese" ] [dependencies] erg_common = { version = "0.2.5", path = "../erg_common" } diff --git a/compiler/erg_compiler/error.rs b/compiler/erg_compiler/error.rs index c2f45080..cf221c0e 100644 --- a/compiler/erg_compiler/error.rs +++ b/compiler/erg_compiler/error.rs @@ -138,10 +138,20 @@ impl CompileError { fn_name: &str, line: u32, ) -> Self { - Self::new(ErrorCore::new(errno, CompilerSystemError, loc, switch_lang!( - format!("this is a bug of the Erg compiler, please report it to https://github.com/...\ncaused from: {fn_name}:{line}"), - format!("これはErg compilerのバグです、開発者に報告して下さい (https://github.com/...)\n{fn_name}:{line}より発生") - ), None), input, "".into()) + Self::new( + ErrorCore::new( + errno, + CompilerSystemError, + loc, + switch_lang!( + "japanese" => format!("これはErg compilerのバグです、開発者に報告して下さい (https://github.com/...)\n{fn_name}:{line}より発生"), + "english" => format!("this is a bug of the Erg compiler, please report it to https://github.com/...\ncaused from: {fn_name}:{line}"), + ), + None, + ), + input, + "".into(), + ) } pub fn stack_bug( @@ -151,14 +161,24 @@ impl CompileError { block_id: usize, fn_name: &str, ) -> Self { - Self::new(ErrorCore::new(0, CompilerSystemError, loc, switch_lang!( - format!("the number of elements in the stack is invalid (num of elems: {stack_len}, block id: {block_id})\n\ - this is a bug of the Erg compiler, please report it (https://github.com/...)\n\ - caused from: {fn_name}"), - format!("スタックの要素数が異常です (要素数: {stack_len}, ブロックID: {block_id})\n\ - これはコンパイラのバグです、開発者に報告して下さい (https://github.com/...)\n\ - {fn_name}より発生") - ), None), input, "".into()) + Self::new( + ErrorCore::new( + 0, + CompilerSystemError, + loc, + switch_lang!( + "japanese" => format!("スタックの要素数が異常です (要素数: {stack_len}, ブロックID: {block_id})\n\ + これはコンパイラのバグです、開発者に報告して下さい (https://github.com/...)\n\ + {fn_name}より発生"), + "english" => format!("the number of elements in the stack is invalid (num of elems: {stack_len}, block id: {block_id})\n\ + this is a bug of the Erg compiler, please report it (https://github.com/...)\n\ + caused from: {fn_name}"), + ), + None, + ), + input, + "".into(), + ) } pub fn feature_error(input: Input, loc: Location, name: &str, caused_by: Str) -> Self { @@ -168,8 +188,9 @@ impl CompileError { FeatureError, loc, switch_lang!( - format!("this feature({name}) is not implemented yet"), - format!("この機能({name})はまだ正式に提供されていません") + "japanese" => format!("この機能({name})はまだ正式に提供されていません"), + "simplified_chinese" => format!("该功能({name})还没有正式提供"), + "english" => format!("this feature({name}) is not implemented yet"), ), None, ), @@ -195,10 +216,19 @@ impl TyCheckError { } pub fn checker_bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self { - Self::new(ErrorCore::new(errno, CompilerSystemError, loc, switch_lang!( - format!("this is a bug of the Erg compiler, please report it to https://github.com/...\ncaused from: {fn_name}:{line}"), - format!("これはErg compilerのバグです、開発者に報告して下さい (https://github.com/...)\n{fn_name}:{line}より発生") - ), None), "".into()) + Self::new( + ErrorCore::new( + errno, + CompilerSystemError, + loc, + switch_lang!( + "japanese" => format!("これはErg compilerのバグです、開発者に報告して下さい (https://github.com/...)\n{fn_name}:{line}より発生"), + "english" => format!("this is a bug of the Erg compiler, please report it to https://github.com/...\ncaused from: {fn_name}:{line}"), + ), + None, + ), + "".into(), + ) } pub fn feature_error(loc: Location, name: &str, caused_by: Str) -> Self { @@ -208,8 +238,8 @@ impl TyCheckError { FeatureError, loc, switch_lang!( - format!("this feature({name}) is not implemented yet"), - format!("この機能({name})はまだ正式に提供されていません") + "japanese" => format!("この機能({name})はまだ正式に提供されていません"), + "english" => format!("this feature({name}) is not implemented yet"), ), None, ), @@ -238,8 +268,8 @@ impl TyCheckError { NameError, loc, switch_lang!( - format!("{name} is already declared"), - format!("{name}は既に宣言されています") + "japanese" => format!("{name}は既に宣言されています"), + "english" => format!("{name} is already declared"), ), Option::::None, ), @@ -255,12 +285,18 @@ impl TyCheckError { found_t: &Type, ) -> Self { let name = readable_name(name); - Self::new(ErrorCore::new(0, TypeError, loc, - switch_lang!( - format!("{name} was declared as {GREEN}{spec_t}{RESET}, but an {RED}{found_t}{RESET} object is assigned"), - format!("{name}は{GREEN}{spec_t}{RESET}型として宣言されましたが、{RED}{found_t}{RESET}型のオブジェクトが代入されています") - ), Option::::None), - caused_by + Self::new( + ErrorCore::new( + 0, + TypeError, + loc, + switch_lang!( + "japanese" => format!("{name}は{GREEN}{spec_t}{RESET}型として宣言されましたが、{RED}{found_t}{RESET}型のオブジェクトが代入されています"), + "english" => format!("{name} was declared as {GREEN}{spec_t}{RESET}, but an {RED}{found_t}{RESET} object is assigned"), + ), + Option::::None, + ), + caused_by, ) } @@ -272,8 +308,8 @@ impl TyCheckError { TypeError, loc, switch_lang!( - format!("the type of {name} is not specified"), - format!("{name}の型が指定されていません") + "japanese" => format!("{name}の型が指定されていません"), + "english" => format!("the type of {name} is not specified"), ), None, ), @@ -291,8 +327,8 @@ impl TyCheckError { let hint = similar_name.map(|n| { let n = readable_name(n); switch_lang!( - format!("exists a similar name variable: {n}"), - format!("似た名前の変数があります: {n}") + "japanese" => format!("似た名前の変数があります: {n}"), + "english" => format!("exists a similar name variable: {n}"), ) .into() }); @@ -302,8 +338,8 @@ impl TyCheckError { NameError, loc, switch_lang!( - format!("{RED}{name}{RESET} is not defined"), - format!("{RED}{name}{RESET}という変数は定義されていません") + "japanese" => format!("{RED}{name}{RESET}という変数は定義されていません"), + "english" => format!("{RED}{name}{RESET} is not defined"), ), hint, ), @@ -321,8 +357,8 @@ impl TyCheckError { let hint = similar_name.map(|n| { let n = readable_name(n); switch_lang!( - format!("has a similar name attribute: {n}"), - format!("似た名前の属性があります: {n}") + "japanese" => format!("似た名前の属性があります: {n}"), + "english" => format!("has a similar name attribute: {n}"), ) .into() }); @@ -332,8 +368,8 @@ impl TyCheckError { AttributeError, loc, switch_lang!( - format!("{obj_t} object has no attribute {RED}{name}{RESET}"), - format!("{obj_t}型オブジェクトに{RED}{name}{RESET}という属性はありません") + "japanese" => format!("{obj_t}型オブジェクトに{RED}{name}{RESET}という属性はありません"), + "english" => format!("{obj_t} object has no attribute {RED}{name}{RESET}"), ), hint, ), @@ -353,12 +389,12 @@ impl TyCheckError { NotImplementedError, callee.loc(), switch_lang!( - format!( + "japanese" => format!( + "{callee}は{param_ts}を引数に取る呼び出し可能オブジェクトではありません" + ), + "english" => format!( "{callee} is not a Callable object that takes {param_ts} as an argument" ), - format!( - "{callee}は{param_ts}を引数に取る呼び出し可能オブジェクトではありません" - ) ), None, ), @@ -373,10 +409,19 @@ impl TyCheckError { expect: &Type, found: &Type, ) -> Self { - Self::new(ErrorCore::new(0, TypeError, loc, switch_lang!( - format!("the type of {name} is mismatched:\nexpected: {GREEN}{expect}{RESET}\nbut found: {RED}{found}{RESET}"), - format!("{name}の型が違います。\n予期した型: {GREEN}{expect}{RESET}\n与えられた型: {RED}{found}{RESET}") - ), None), caused_by) + Self::new( + ErrorCore::new( + 0, + TypeError, + loc, + switch_lang!( + "japanese" => format!("{name}の型が違います。\n予期した型: {GREEN}{expect}{RESET}\n与えられた型: {RED}{found}{RESET}"), + "english" => format!("the type of {name} is mismatched:\nexpected: {GREEN}{expect}{RESET}\nbut found: {RED}{found}{RESET}"), + ), + None, + ), + caused_by, + ) } pub fn return_type_error( @@ -386,10 +431,19 @@ impl TyCheckError { expect: &Type, found: &Type, ) -> Self { - Self::new(ErrorCore::new(0, TypeError, loc, switch_lang!( - format!("the return type of {name} is mismatched:\nexpected: {GREEN}{expect}{RESET}\nbut found: {RED}{found}{RESET}"), - format!("{name}の戻り値の型が違います。\n予期した型: {GREEN}{expect}{RESET}\n与えられた型: {RED}{found}{RESET}") - ), None), caused_by) + Self::new( + ErrorCore::new( + 0, + TypeError, + loc, + switch_lang!( + "japanese" => format!("{name}の戻り値の型が違います。\n予期した型: {GREEN}{expect}{RESET}\n与えられた型: {RED}{found}{RESET}"), + "english" => format!("the return type of {name} is mismatched:\nexpected: {GREEN}{expect}{RESET}\nbut found: {RED}{found}{RESET}"), + ), + None, + ), + caused_by, + ) } pub fn uninitialized_error(loc: Location, caused_by: Str, name: &str, t: &Type) -> Self { @@ -399,8 +453,8 @@ impl TyCheckError { NameError, loc, switch_lang!( - format!("{name}: {t} is not initialized"), - format!("{name}: {t}は初期化されていません") + "japanese" => format!("{name}: {t}は初期化されていません"), + "english" => format!("{name}: {t} is not initialized"), ), None, ), @@ -409,10 +463,19 @@ impl TyCheckError { } pub fn argument_error(loc: Location, caused_by: Str, expect: usize, found: usize) -> Self { - Self::new(ErrorCore::new(0, TypeError, loc, switch_lang!( - format!("the number of positional arguments is mismatched:\nexpected: {GREEN}{expect}{RESET}\nbut found: {RED}{found}{RESET}"), - format!("ポジショナル引数の数が違います。\n予期した個数: {GREEN}{expect}{RESET}\n与えられた個数: {RED}{found}{RESET}") - ), None), caused_by) + Self::new( + ErrorCore::new( + 0, + TypeError, + loc, + switch_lang!( + "japanese" => format!("ポジショナル引数の数が違います。\n予期した個数: {GREEN}{expect}{RESET}\n与えられた個数: {RED}{found}{RESET}"), + "english" => format!("the number of positional arguments is mismatched:\nexpected: {GREEN}{expect}{RESET}\nbut found: {RED}{found}{RESET}"), + ), + None, + ), + caused_by, + ) } pub fn match_error(loc: Location, caused_by: Str, expr_t: &Type) -> Self { @@ -422,8 +485,8 @@ impl TyCheckError { TypeError, loc, switch_lang!( - format!("not all patterns of type {expr_t} are covered"), - format!("{expr_t}型の全パターンを網羅していません") + "japanese" => format!("{expr_t}型の全パターンを網羅していません"), + "english" => format!("not all patterns of type {expr_t} are covered"), ), None, ), @@ -438,8 +501,8 @@ impl TyCheckError { TypeError, loc, switch_lang!( - format!("failed to infer the type of {expr}"), - format!("{expr}の型が推論できません") + "japanese" => format!("{expr}の型が推論できません"), + "english" => format!("failed to infer the type of {expr}"), ), None, ), @@ -463,8 +526,8 @@ impl TyCheckError { AssignError, loc, switch_lang!( - format!("cannot assign twice to the immutable variable {name}"), - format!("定数{name}には再代入できません") + "japanese" => format!("定数{name}には再代入できません"), + "english" => format!("cannot assign twice to the immutable variable {name}"), ), None, ), @@ -487,18 +550,18 @@ impl TyCheckError { TypeError, loc, switch_lang!( - format!( + "japanese" => format!( + "{name}に渡された引数の数が多すぎます。 +必要な引数の合計数: {GREEN}{params_len}{RESET}個 +渡された引数の数: {RED}{pos_args_len}{RESET}個 +キーワード引数の数: {RED}{kw_args_len}{RESET}個" + ), + "english" => format!( "too many arguments for {name}: total expected params: {GREEN}{params_len}{RESET} passed positional args: {RED}{pos_args_len}{RESET} passed keyword args: {RED}{kw_args_len}{RESET}" ), - format!( - "{name}に渡された引数の数が多すぎます。 -必要な引数の合計数: {GREEN}{params_len}{RESET}個 -渡された引数の数: {RED}{pos_args_len}{RESET}個 -キーワード引数の数: {RED}{kw_args_len}{RESET}個" - ) ), None, ), @@ -519,8 +582,8 @@ passed keyword args: {RED}{kw_args_len}{RESET}" TypeError, loc, switch_lang!( - format!("{name}'s argument {RED}{arg_name}{RESET} is passed multiple times"), - format!("{name}の引数{RED}{arg_name}{RESET}が複数回渡されています") + "japanese" => format!("{name}の引数{RED}{arg_name}{RESET}が複数回渡されています"), + "english" => format!("{name}'s argument {RED}{arg_name}{RESET} is passed multiple times"), ), None, ), @@ -541,9 +604,9 @@ passed keyword args: {RED}{kw_args_len}{RESET}" TypeError, loc, switch_lang!( - format!("{name} got unexpected keyword argument {RED}{param_name}{RESET}"), - format!("{name}には予期しないキーワード引数{RED}{param_name}{RESET}が渡されています") - ), + "japanese" => format!("{name}には予期しないキーワード引数{RED}{param_name}{RESET}が渡されています"), + "english" => format!("{name} got unexpected keyword argument {RED}{param_name}{RESET}"), + ), None, ), caused_by, @@ -558,8 +621,8 @@ passed keyword args: {RED}{kw_args_len}{RESET}" UnusedWarning, loc, switch_lang!( - format!("{YELLOW}{name}{RESET} is not used"), - format!("{YELLOW}{name}{RESET}は使用されていません") + "japanese" => format!("{YELLOW}{name}{RESET}は使用されていません"), + "english" => format!("{YELLOW}{name}{RESET} is not used"), ), None, ), @@ -580,10 +643,19 @@ passed keyword args: {RED}{kw_args_len}{RESET}" (None, Some(r)) => r, (None, None) => Location::Unknown, }; - Self::new(ErrorCore::new(0, TypeError, loc, switch_lang!( - format!("unification failed:\nlhs: {YELLOW}{lhs_t}{RESET}\nrhs: {YELLOW}{rhs_t}{RESET}"), - format!("型の単一化に失敗しました:\n左辺: {YELLOW}{lhs_t}{RESET}\n右辺: {YELLOW}{rhs_t}{RESET}") - ), None), caused_by) + Self::new( + ErrorCore::new( + 0, + TypeError, + loc, + switch_lang!( + "japanese" => format!("型の単一化に失敗しました:\n左辺: {YELLOW}{lhs_t}{RESET}\n右辺: {YELLOW}{rhs_t}{RESET}"), + "english" => format!("unification failed:\nlhs: {YELLOW}{lhs_t}{RESET}\nrhs: {YELLOW}{rhs_t}{RESET}"), + ), + None, + ), + caused_by, + ) } pub fn re_unification_error( @@ -599,10 +671,19 @@ passed keyword args: {RED}{kw_args_len}{RESET}" (None, Some(r)) => r, (None, None) => Location::Unknown, }; - Self::new(ErrorCore::new(0, TypeError, loc, switch_lang!( - format!("re-unification failed:\nlhs: {YELLOW}{lhs_t}{RESET}\nrhs: {YELLOW}{rhs_t}{RESET}"), - format!("型の再単一化に失敗しました:\n左辺: {YELLOW}{lhs_t}{RESET}\n右辺: {YELLOW}{rhs_t}{RESET}") - ), None), caused_by) + Self::new( + ErrorCore::new( + 0, + TypeError, + loc, + switch_lang!( + "japanese" => format!("型の再単一化に失敗しました:\n左辺: {YELLOW}{lhs_t}{RESET}\n右辺: {YELLOW}{rhs_t}{RESET}"), + "english" => format!("re-unification failed:\nlhs: {YELLOW}{lhs_t}{RESET}\nrhs: {YELLOW}{rhs_t}{RESET}"), + ), + None, + ), + caused_by, + ) } pub fn subtyping_error( @@ -618,17 +699,35 @@ passed keyword args: {RED}{kw_args_len}{RESET}" (None, Some(r)) => r, (None, None) => Location::Unknown, }; - Self::new(ErrorCore::new(0, TypeError, loc, switch_lang!( - format!("subtype constraints cannot be satisfied:\nsubtype: {YELLOW}{sub_t}{RESET}\nsupertype: {YELLOW}{sup_t}{RESET}"), - format!("部分型制約を満たせません:\nサブタイプ: {YELLOW}{sub_t}{RESET}\nスーパータイプ: {YELLOW}{sup_t}{RESET}") - ), None), caused_by) + Self::new( + ErrorCore::new( + 0, + TypeError, + loc, + switch_lang!( + "japanese" => format!("部分型制約を満たせません:\nサブタイプ: {YELLOW}{sub_t}{RESET}\nスーパータイプ: {YELLOW}{sup_t}{RESET}"), + "english" => format!("subtype constraints cannot be satisfied:\nsubtype: {YELLOW}{sub_t}{RESET}\nsupertype: {YELLOW}{sup_t}{RESET}"), + ), + None, + ), + caused_by, + ) } pub fn pred_unification_error(lhs: &Predicate, rhs: &Predicate, caused_by: Str) -> Self { - Self::new(ErrorCore::new(0, TypeError, Location::Unknown, switch_lang!( - format!("predicate unification failed:\nlhs: {YELLOW}{lhs}{RESET}\nrhs: {YELLOW}{rhs}{RESET}"), - format!("述語式の単一化に失敗しました:\n左辺: {YELLOW}{lhs}{RESET}\n右辺: {YELLOW}{rhs}{RESET}") - ), None), caused_by) + Self::new( + ErrorCore::new( + 0, + TypeError, + Location::Unknown, + switch_lang!( + "japanese" => format!("述語式の単一化に失敗しました:\n左辺: {YELLOW}{lhs}{RESET}\n右辺: {YELLOW}{rhs}{RESET}"), + "english" => format!("predicate unification failed:\nlhs: {YELLOW}{lhs}{RESET}\nrhs: {YELLOW}{rhs}{RESET}"), + ), + None, + ), + caused_by, + ) } pub fn has_effect>(expr: &Expr, caused_by: S) -> Self { @@ -638,8 +737,8 @@ passed keyword args: {RED}{kw_args_len}{RESET}" HasEffect, expr.loc(), switch_lang!( - format!("this expression causes a side-effect"), - format!("この式には副作用があります") + "japanese" => format!("この式には副作用があります"), + "english" => format!("this expression causes a side-effect"), ), None, ), @@ -659,14 +758,14 @@ passed keyword args: {RED}{kw_args_len}{RESET}" MoveError, name_loc, switch_lang!( - format!( + "japanese" => format!( + "{RED}{name}{RESET}は{}行目ですでに移動されています", + moved_loc.ln_begin().unwrap() + ), + "english" => format!( "{RED}{name}{RESET} was moved in line {}", moved_loc.ln_begin().unwrap() ), - format!( - "{RED}{name}{RESET}は{}行目ですでに移動されています", - moved_loc.ln_begin().unwrap() - ) ), None, ), diff --git a/compiler/erg_compiler/lower.rs b/compiler/erg_compiler/lower.rs index f15d0bde..b204821f 100644 --- a/compiler/erg_compiler/lower.rs +++ b/compiler/erg_compiler/lower.rs @@ -62,13 +62,13 @@ impl ASTLowerer { expr.loc(), self.ctx.name.clone(), switch_lang!( - "the evaluation result of the expression is not used", - "式の評価結果が使われていません", + "japanese" => "式の評価結果が使われていません", + "english" => "the evaluation result of the expression is not used", ), Some( switch_lang!( - "if you don't use the value, use `discard` function", - "値を使わない場合は、discard関数を使用してください", + "japanese" => "値を使わない場合は、discard関数を使用してください", + "english" => "if you don't use the value, use `discard` function", ) .into(), ), diff --git a/compiler/erg_parser/Cargo.toml b/compiler/erg_parser/Cargo.toml index 3753d02a..6c6af346 100644 --- a/compiler/erg_parser/Cargo.toml +++ b/compiler/erg_parser/Cargo.toml @@ -12,6 +12,8 @@ homepage = "https://erg-lang.github.io/" [features] debug = [ "erg_common/debug" ] japanese = [ "erg_common/japanese" ] +simplified_chinese = [ "erg_common/simplified_chinese" ] +traditional_chinese = [ "erg_common/traditional_chinese" ] [dependencies] erg_common = { version = "0.2.5", path = "../erg_common" } diff --git a/compiler/erg_parser/error.rs b/compiler/erg_parser/error.rs index 694beeb9..a7874e48 100644 --- a/compiler/erg_parser/error.rs +++ b/compiler/erg_parser/error.rs @@ -21,10 +21,16 @@ impl LexError { } pub fn compiler_bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self { - Self::new(ErrorCore::new(errno, CompilerSystemError, loc, switch_lang!( - format!("this is a bug of the Erg compiler, please report it to https://github.com/mtshiba/erg\ncaused from: {fn_name}:{line}"), - format!("これはErg compilerのバグです、開発者に報告して下さい (https://github.com/mtshiba/erg)\n{fn_name}:{line}より発生") - ), None)) + Self::new(ErrorCore::new( + errno, + CompilerSystemError, + loc, + switch_lang!( + "japanese" => format!("これはErg compilerのバグです、開発者に報告して下さい (https://github.com/mtshiba/erg)\n{fn_name}:{line}より発生"), + "english" => format!("this is a bug of the Erg compiler, please report it to https://github.com/mtshiba/erg\ncaused from: {fn_name}:{line}"), + ), + None, + )) } pub fn feature_error(errno: usize, loc: Location, name: &str) -> Self { @@ -33,8 +39,9 @@ impl LexError { FeatureError, loc, switch_lang!( - format!("this feature({name}) is not implemented yet"), - format!("この機能({name})はまだ正式に提供されていません") + "japanese" => format!("この機能({name})はまだ正式に提供されていません"), + "simplified_chinese" => format!("该功能({name})还没有正式提供"), + "english" => format!("this feature({name}) is not implemented yet"), ), None, )) @@ -45,7 +52,11 @@ impl LexError { errno, SyntaxError, loc, - switch_lang!("invalid syntax", "不正な構文です"), + switch_lang!( + "japanese" => "不正な構文です", + "simplified_chinese" => "无效的语法", + "english" => "invalid syntax", + ), None, )) } diff --git a/compiler/erg_parser/lex.rs b/compiler/erg_parser/lex.rs index 456a2b68..9c35e5fe 100644 --- a/compiler/erg_parser/lex.rs +++ b/compiler/erg_parser/lex.rs @@ -266,8 +266,8 @@ impl Lexer /*<'a>*/ { 0, comment.loc(), switch_lang!( - "invalid unicode character (bi-directional override) in comments", - "不正なユニコード文字(双方向オーバーライド)がコメント中に使用されています" + "japanese" => "不正なユニコード文字(双方向オーバーライド)がコメント中に使用されています", + "english" => "invalid unicode character (bi-directional override) in comments", ), None, )); @@ -298,7 +298,10 @@ impl Lexer /*<'a>*/ { Some(Err(LexError::syntax_error( 0, space.loc(), - switch_lang!("invalid indent", "インデントが不正です"), + switch_lang!( + "japanese" => "インデントが不正です", + "english" => "invalid indent", + ), None, ))) } else if self.prev_token.is(Newline) { @@ -317,11 +320,14 @@ impl Lexer /*<'a>*/ { return Some(Err(LexError::syntax_error( 0, token.loc(), - switch_lang!("indentation is too deep", "インデントが深すぎます"), + switch_lang!( + "japanese" => "インデントが深すぎます", + "english" => "indentation is too deep", + ), Some( switch_lang!( - "The code is too complicated. Please split the process.", - "コードが複雑すぎます。処理を分割してください" + "japanese" => "コードが複雑すぎます。処理を分割してください", + "english" => "The code is too complicated. Please split the process", ) .into(), ), @@ -359,7 +365,10 @@ impl Lexer /*<'a>*/ { Some(Err(LexError::syntax_error( 0, invalid_dedent.loc(), - switch_lang!("invalid indent", "インデントが不正です"), + switch_lang!( + "japanese" => "インデントが不正です", + "english" => "invalid indent", + ), None, ))) } @@ -529,9 +538,9 @@ impl Lexer /*<'a>*/ { 0, token.loc(), switch_lang!( - "invalid unicode character (bi-directional override) in string literal", - "不正なユニコード文字(双方向オーバーライド)が文字列中に使用されています" - ), + "japanese" => "不正なユニコード文字(双方向オーバーライド)が文字列中に使用されています", + "english" => "invalid unicode character (bi-directional override) in string literal", + ), None, )); } @@ -542,8 +551,8 @@ impl Lexer /*<'a>*/ { 0, token.loc(), switch_lang!( - "the string is not closed by \"", - "文字列が\"によって閉じられていません" + "japanese" => "文字列が\"によって閉じられていません", + "english" => "the string is not closed by \"", ), None, )) @@ -590,7 +599,10 @@ impl Iterator for Lexer /*<'a>*/ { Some(Err(LexError::syntax_error( 0, token.loc(), - switch_lang!("no such operator: <.", "<.という演算子はありません"), + switch_lang!( + "japanese" => "<.という演算子はありません", + "english" => "no such operator: <.", + ), None, ))) } @@ -764,8 +776,17 @@ impl Iterator for Lexer /*<'a>*/ { Some(Err(LexError::syntax_error( 0, token.loc(), - switch_lang!("cannot use a tab as a space", "タブ文字は使用できません"), - Some(switch_lang!("use spaces", "スペースを使用してください").into()), + switch_lang!( + "japanese" => "タブ文字は使用できません", + "english" => "cannot use a tab as a space", + ), + Some( + switch_lang!( + "japanese" => "スペース( )を使用してください", + "english" => "use spaces ( )", + ) + .into(), + ), ))) } // TODO: @@ -788,8 +809,8 @@ impl Iterator for Lexer /*<'a>*/ { 0, token.loc(), switch_lang!( - format!("`{}` cannot be defined by user", &token.content), - format!("`{}`はユーザー定義できません", &token.content) + "japanese" => format!("`{}`はユーザー定義できません", &token.content), + "english" => format!("`{}` cannot be defined by user", &token.content), ), None, ))); @@ -802,8 +823,8 @@ impl Iterator for Lexer /*<'a>*/ { 0, token.loc(), switch_lang!( - format!("back quotes (`) not closed"), - format!("バッククォート(`)が閉じられていません") + "japanese" => format!("バッククォート(`)が閉じられていません"), + "english" => format!("back quotes (`) not closed"), ), None, ))) @@ -819,8 +840,8 @@ impl Iterator for Lexer /*<'a>*/ { 0, token.loc(), switch_lang!( - format!("invalid character: '{invalid}'"), - format!("この文字は使用できません: '{invalid}'") + "japanese" => format!("この文字は使用できません: '{invalid}'"), + "english" => format!("invalid character: '{invalid}'"), ), None, ))) diff --git a/compiler/erg_parser/parse.rs b/compiler/erg_parser/parse.rs index 8dc2f35b..a8612d8c 100644 --- a/compiler/erg_parser/parse.rs +++ b/compiler/erg_parser/parse.rs @@ -439,7 +439,10 @@ impl Parser { let err = ParseError::syntax_error( 0, loc, - switch_lang!("failed to parse a block", "ブロックの解析に失敗しました"), + switch_lang!( + "japanese" => "ブロックの解析に失敗しました", + "english" => "failed to parse a block", + ), None, ); Err(err) @@ -496,8 +499,8 @@ impl Parser { 0, self.peek().unwrap().loc(), switch_lang!( - "Cannot use type bounds in a declaration of a variable", - "変数宣言で型制約は使えません" + "japanese" => "変数宣言で型制約は使えません", + "english" => "Cannot use type bounds in a declaration of a variable", ), None, ); @@ -509,16 +512,6 @@ impl Parser { self.skip(); Some(self.try_reduce_type_spec()?) } else { - if self.cur_is(Colon) { - self.warns.push(ParseError::syntax_warning(0, name.loc(), switch_lang!( - "Since it is obvious that the variable is of type `Type`, there is no need to specify the type.", - "変数がType型であることは明らかなので、型指定は不要です。" - ), Some(switch_lang!( - "Are you sure you're not confusing it with subclass declaration (<:)?", - "サブクラスの宣言(<:)ではありませんか?" - ).into()) - )); - } None } } else if self.cur_is(Colon) { @@ -663,8 +656,8 @@ impl Parser { 0, t.loc(), switch_lang!( - "Binary operators cannot be used in left-values", - "左辺値の中で中置演算子は使えません" + "japanese" => "左辺値の中で中置演算子は使えません", + "english" => "Binary operators cannot be used in left-values", ), None, ); @@ -754,9 +747,10 @@ impl Parser { return Err(ParseError::syntax_error( 0, param.loc(), + // TODO: switch_lang! "non-default argument follows default argument", None, - )) + )); } (false, false) => { non_default_params.push(param); @@ -768,8 +762,8 @@ impl Parser { 0, t.loc(), switch_lang!( - "Binary operators cannot be used in parameters", - "仮引数の中で中置演算子は使えません" + "japanese" => "仮引数の中で中置演算子は使えません", + "english" => "Binary operators cannot be used in parameters", ), None, ); @@ -1015,18 +1009,21 @@ impl Parser { fn validate_const_expr(&mut self, expr: Expr) -> ParseResult { match expr { - Expr::Lit(l) => Ok(ConstExpr::Lit(l)), + Expr::Lit(l) => Ok(ConstExpr::Lit(l)), Expr::Accessor(Accessor::Local(local)) => { let local = ConstLocal::new(local.symbol); Ok(ConstExpr::Accessor(ConstAccessor::Local(local))) } // TODO: App, Array, Record, BinOp, UnaryOp, - other => { - Err(ParseError::syntax_error(0, other.loc(), switch_lang!( - "this expression is not computable at the compile-time, so cannot used as a type-argument", - "この式はコンパイル時計算できないため、型引数には使用できません", - ), None)) - } + other => Err(ParseError::syntax_error( + 0, + other.loc(), + switch_lang!( + "japanese" => "この式はコンパイル時計算できないため、型引数には使用できません", + "english" => "this expression is not computable at the compile-time, so cannot used as a type-argument", + ), + None, + )), } } diff --git a/doc/EN/dev_guide/i18n_messages.md b/doc/EN/dev_guide/i18n_messages.md new file mode 100644 index 00000000..7736ce80 --- /dev/null +++ b/doc/EN/dev_guide/i18n_messages.md @@ -0,0 +1,59 @@ +# Multilingualization of Messages + +Erg is working on making all messages (start, option, doc, hint, warning, error messages, etc.) multilingual within the language. +This project is open to anyone without detailed knowledge of Rust or Erg. Your participation is always welcome. + +Here is how to translate them. + +## Search `switch_lang!` + +In the Erg source code, look for the item `switch_lang!` (use grep or your editor's search function). +You should find something like this: + +```rust +switch_lang!( + "japanese" => format!("この機能({name})はまだ正式に提供されていません"), + "english" => format!("this feature({name}) is not implemented yet"), +), +``` + +This message is currently supported only in Japanese and English. Let's add a simplified Chinese message as a test. + +## Add a New Message + +Add translated messages as you see the content in other languages. Don't forget the comma (`,`) last. + +```rust +switch_lang!( + "japanese" => format!("この機能({name})はまだ正式に提供されていません"), + "simplified_chinese" => format!("该功能({name})还没有正式提供"), + "english" => format!("this feature({name}) is not implemented yet"), +), +``` + +Note that English is the default and must come last. +The `{name}` part is a Rust formatting feature that allows you to embed the contents of a variable (`name`) into a string. + +## Build + +Now, let's build with the `--features simplified_chinese` option. + +screenshot_i18n_messages + +We did it! + +## FAQ + +Q: What does a specification like `{RED}{foo}{RESET}` mean? +A: {RED} and subsequent letters will be displayed in red. {RESET} will restore the color. + +Q: If I want to add my language, how do I replace the `"simplified_chinese" =>` part? + +The following languages are currently supported: + +* "english" (default) +* "japanese" +* "simplified_chinese" +* "traditional_chinese" + +If you would like to add languages other than these, please make a request. diff --git a/doc/JA/dev_guide/i18n_messages.md b/doc/JA/dev_guide/i18n_messages.md new file mode 100644 index 00000000..675d3f3d --- /dev/null +++ b/doc/JA/dev_guide/i18n_messages.md @@ -0,0 +1,58 @@ +# Multilingualization of Messages + +Ergはメッセージ(スタート、オプション、ドキュメント、ヒント、警告、エラーメッセージなど)の多言語化を進めています。 +このプロジェクトは、RustやErgの詳しい知識がなくても参加することができます。ぜひ協力をお願いします。 + +以下に、多言語化の方法を説明します。 + +## `switch_lang!`を探す + +Ergのソースコードの中で、`switch_lang!`という項目を探します(grepやエディタの検索機能を使ってください)。 +以下のようなものが見つかるはずです。 + +```rust +switch_lang!( + "japanese" => format!("この機能({name})はまだ正式に提供されていません"), + "english" => format!("this feature({name}) is not implemented yet"), +), +``` + +このメッセージは現在、日本語と英語のみでサポートされています。試しに簡体字のメッセージを追加してみましょう。 + +## メッセージを追加する + +他の言語の内容を見ながら、翻訳されたメッセージを追加してください。最後にカンマ(`,`)を忘れないでください。 + +```rust +switch_lang!( + "japanese" => format!("この機能({name})はまだ正式に提供されていません"), + "simplified_chinese" => format!("该功能({name})还没有正式提供"), + "english" => format!("this feature({name}) is not implemented yet"), +), +``` + +なお、英語はデフォルトであり、必ず最後に来るようにします。 +`{name}` の部分は Rust のフォーマット機能で、変数の内容 (`name`) を文字列に埋め込むことができます。 + +## Build + +では、`--features simplified_chinese` オプションを付けてビルドしてみましょう。 + +screenshot_i18n_messages + +やりましたね! + +## FAQ + +Q: `{RED}{foo}{RESET}` のような指定は何を意味するのでしょうか? +A: {RED}以降が赤色で表示されます。{RESET}で色を元に戻します。 + +Q: 自分の言語を追加したい場合、`"simplified_chinese" =>`の部分はどのように置き換えればよいですか? +A: 現在、以下の言語がサポートされています。 + +* "english" (デフォルト) +* "japanese" (日本語) +* "simplified_chinese" (簡体字中国語) +* "traditional_chinese"(繁体字中国語) + +これら以外の言語を追加したい場合は、リクエストしてください。