From d9a934128a2b33115f15f7825122853408de0589 Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Fri, 2 Oct 2026 00:28:22 +0800 Subject: [PATCH] docs: start the bilingual sweep with the foundation layer apsis-units and apsis-core are done, 86 of 180 comment blocks in the layer. The checker in scripts/check-bilingual.py reports which blocks still lack Chinese, and also catches the two mistakes that are easy to make while adding a language: a mangled byte sequence, and an English word left inside a Chinese sentence, which is what happened once in quantity.rs and is now caught mechanically. Translations keep terms that have a standard Chinese name in Chinese and leave proper nouns, abbreviations and method names in English, as the glossary sets out. The English text is never reworded. --- crates/foundation/apsis-core/src/constant.rs | 86 +++++++++++++ crates/foundation/apsis-core/src/error.rs | 54 +++++++- crates/foundation/apsis-core/src/quantity.rs | 21 ++++ crates/foundation/apsis-units/src/angle.rs | 30 +++++ .../foundation/apsis-units/src/dimension.rs | 16 +++ .../foundation/apsis-units/src/frequency.rs | 15 +++ crates/foundation/apsis-units/src/length.rs | 22 ++++ crates/foundation/apsis-units/src/lib.rs | 14 +++ crates/foundation/apsis-units/src/macros.rs | 11 ++ .../foundation/apsis-units/src/temperature.rs | 24 ++++ crates/foundation/apsis-units/src/time.rs | 11 ++ scripts/check-bilingual.py | 119 ++++++++++++++++++ 12 files changed, 421 insertions(+), 2 deletions(-) create mode 100755 scripts/check-bilingual.py diff --git a/crates/foundation/apsis-core/src/constant.rs b/crates/foundation/apsis-core/src/constant.rs index 8011b51..4dbcf03 100644 --- a/crates/foundation/apsis-core/src/constant.rs +++ b/crates/foundation/apsis-core/src/constant.rs @@ -19,10 +19,17 @@ //! Where a value is defined exactly by a standard, the doc comment says so, //! because several derived quantities depend on the exact value rather than //! the rounded printed one. +//! +//! 当某个数值由标准精确定义时,文档注释会指出这一点,因为若干导出量依赖精确值 +//! 而非取整后的打印值。 /// Speed of light in vacuum, in meters per second. /// /// Exact by definition of the metre in the SI. +/// +/// 真空光速,单位为米每秒。 +/// +/// 由 SI 对米的定义精确确定。 pub const SPEED_OF_LIGHT: f64 = 299_792_458.0; /// Newtonian constant of gravitation, in cubic meters per kilogram per @@ -30,37 +37,65 @@ pub const SPEED_OF_LIGHT: f64 = 299_792_458.0; /// /// CODATA 2018 value with its standard uncertainty of /// `1.5e-11 m^3 kg^-1 s^-2`. +/// +/// 牛顿万有引力常数,单位为立方米每千克每二次方秒。 +/// +/// CODATA 2018 推荐值,标准不确定度为 `1.5e-11 m^3 kg^-1 s^-2`。 pub const GRAVITATIONAL_CONSTANT: f64 = 6.674_30e-11; /// Boltzmann constant, in joules per kelvin. /// /// Exact by definition of the kelvin in the SI. +/// +/// 玻尔兹曼常量,单位为焦耳每开尔文。 +/// +/// 由 SI 对开尔文的定义精确确定。 pub const BOLTZMANN: f64 = 1.380_649e-23; /// Planck constant, in joule seconds. /// /// Exact by definition of the kilogram in the SI. +/// +/// 普朗克常量,单位为焦耳秒。 +/// +/// 由 SI 对千克的定义精确确定。 pub const PLANCK: f64 = 6.626_070_15e-34; /// Elementary charge, in coulombs. /// /// Exact by definition of the ampere in the SI. +/// +/// elementary charge,单位为库仑。 +/// +/// 由 SI 对安培的定义精确确定。 pub const ELEMENTARY_CHARGE: f64 = 1.602_176_634e-19; /// Astronomical unit, in meters. /// /// Exact by IAU 2012 Resolution B2. +/// +/// 天文单位,单位为米。 +/// +/// 由 IAU 2012 Resolution B2 精确确定。 pub const ASTRONOMICAL_UNIT: f64 = 149_597_870_700.0; /// Standard gravity, in meters per second squared. /// /// Exact by definition of the standard atmosphere. +/// +/// 标准重力加速度,单位为米每二次方秒。 +/// +/// 由标准大气的定义精确确定。 pub const STANDARD_GRAVITY: f64 = 9.806_65; /// One minute, in seconds. +/// +/// 一分钟,单位为秒。 pub const MINUTE: f64 = 60.0; /// One hour, in seconds. +/// +/// 一小时,单位为秒。 pub const HOUR: f64 = 3_600.0; /// One day of 24 hours, in seconds. @@ -68,32 +103,57 @@ pub const HOUR: f64 = 3_600.0; /// This is the SI day. It is not the mean solar day, which is slightly /// longer, and not the Julian day of 86 400 seconds used in Julian date /// arithmetic, which is identical to this value. +/// +/// 一个 24 小时的日,单位为秒。 +/// +/// 这是 SI 日。它不是略长的平均太阳日,也不是儒略日运算中使用的 86 400 秒 +/// 儒略日,后者与本值相同。 pub const DAY: f64 = 86_400.0; /// One Julian year of 365.25 days, in seconds. /// /// Exact by definition of the Julian year. +/// +/// 一个 365.25 日的儒略年,单位为秒。 +/// +/// 由儒略年的定义精确确定。 pub const JULIAN_YEAR: f64 = 31_557_600.0; /// One Julian century of 36 525 days, in seconds. /// /// Exact by definition of the Julian year, used by precession and secular /// rate expressions quoted per Julian century. +/// +/// 一个 36 525 日的儒略世纪,单位为秒。 +/// +/// 由儒略年的定义精确确定;岁差与长期变化率表达式按儒略世纪引用时使用它。 pub const JULIAN_CENTURY: f64 = 3_155_760_000.0; /// Julian date of the Unix epoch, 1970-01-01T00:00:00 UTC. /// /// This is `2 440 587.5` in the Julian date system. +/// +/// Unix 历元的儒略日,1970-01-01T00:00:00 UTC。 +/// +/// 在儒略日体系中该值为 `2 440 587.5`。 pub const JULIAN_DATE_UNIX_EPOCH: f64 = 2_440_587.5; /// Julian date of the J2000.0 epoch, 2000-01-01T12:00:00 TT. /// /// Exact by definition of the J2000.0 epoch. +/// +/// J2000.0 历元的儒略日,2000-01-01T12:00:00 TT。 +/// +/// 由 J2000.0 历元的定义精确确定。 pub const JULIAN_DATE_J2000: f64 = 2_451_545.0; /// The number of seconds between the J2000.0 epoch and the Unix epoch. /// /// Derived from [`JULIAN_DATE_J2000`] and [`JULIAN_DATE_UNIX_EPOCH`]. +/// +/// J2000.0 历元与 Unix 历元之间的秒数。 +/// +/// 由 [`JULIAN_DATE_J2000`] 与 [`JULIAN_DATE_UNIX_EPOCH`] 导出。 pub const SECONDS_J2000_TO_UNIX: f64 = (JULIAN_DATE_J2000 - JULIAN_DATE_UNIX_EPOCH) * DAY; /// Earth gravitational parameter, in cubic kilometers per second squared. @@ -102,35 +162,61 @@ pub const SECONDS_J2000_TO_UNIX: f64 = (JULIAN_DATE_J2000 - JULIAN_DATE_UNIX_EPO /// dominated by the uncertainty in the Newtonian constant of gravitation. /// Orbit propagation uses kilometers, so the value is given in those units /// rather than in SI. +/// +/// 地球引力参数,单位为立方千米每二次方秒。 +/// +/// WGS 84 取值 `GM = 398 600.4418 km^3/s^2`,其不确定度主要来自牛顿万有引力常数的 +/// 不确定度。轨道传播使用千米,故该值以千米给出而非 SI 单位。 pub const EARTH_MU: f64 = 398_600.441_8; /// Earth equatorial radius, in kilometers. /// /// WGS 84 semi-major axis, `a = 6 378.137 km`. +/// +/// 地球赤道半径,单位为千米。 +/// +/// WGS 84 半长轴,`a = 6 378.137 km`。 pub const EARTH_EQUATORIAL_RADIUS: f64 = 6_378.137; /// Earth flattening, dimensionless. /// /// WGS 84 value, `f = 1 / 298.257 223 563`. +/// +/// 地球扁率,无量纲。 +/// +/// WGS 84 取值 `f = 1 / 298.257 223 563`。 pub const EARTH_FLATTENING: f64 = 1.0 / 298.257_223_563; /// Earth rotation rate, in radians per second. /// /// WGS 84 value. This is the nominal mean angular velocity of the Earth, /// `omega = 7.292 115e-5 rad/s`. +/// +/// WGS 84 取值,即地球的名义平均角速度, +/// `omega = 7.292 115e-5 rad/s`。 pub const EARTH_ROTATION_RATE: f64 = 7.292_115_0e-5; /// Earth second zonal harmonic, dimensionless. /// /// WGS 84 value, `J2 = 1.082 626 683e-3`. +/// +/// WGS 84 取值,`J2 = 1.082 626 683e-3`。 pub const EARTH_J2: f64 = 1.082_626_683e-3; /// Standard atmosphere sea level pressure, in pascals. /// /// Exact by definition of the standard atmosphere. +/// +/// 标准大气海平面气压,单位为帕斯卡。 +/// +/// 由标准大气的定义精确确定。 pub const STANDARD_ATMOSPHERE: f64 = 101_325.0; /// Zero degrees Celsius on the thermodynamic temperature scale, in kelvin. /// /// Exact by definition of the Celsius scale. +/// +/// 热力学温标上零摄氏度对应的温度,单位为开尔文。 +/// +/// 由摄氏标度的定义精确确定。 pub const ZERO_CELSIUS_IN_KELVIN: f64 = 273.15; diff --git a/crates/foundation/apsis-core/src/error.rs b/crates/foundation/apsis-core/src/error.rs index 09be988..11d32e2 100644 --- a/crates/foundation/apsis-core/src/error.rs +++ b/crates/foundation/apsis-core/src/error.rs @@ -4,6 +4,10 @@ //! as a single [`Error`] struct carrying an [`ErrorKind`], an optional //! message, and an optional source. `ErrorKind` is a fieldless enum so that //! callers can match on the failure category without allocating. +//! +//! 工作区不使用外部错误 crate。错误统一建模为单个 [`Error`] 结构体,携带 +//! [`ErrorKind`]、可选消息与可选来源。`ErrorKind` 是无字段枚举,因此调用方可以 +//! 在不分配内存的前提下匹配失败类别。 use core::error::Error as StdError; use core::fmt; @@ -14,29 +18,54 @@ use core::fmt; /// any layer may return any variant. Callers should match on the variant /// that is relevant to their own recovery logic and treat the rest as /// opaque. +/// +/// [`Error`] 的类别。 +/// +/// 各变体按最常产生它们的层分组,但任何层都可以返回任何变体。调用方应只匹配 +/// 与自身恢复逻辑相关的变体,其余视为不透明。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[non_exhaustive] pub enum ErrorKind { /// An input value is outside the range the function accepts. + /// + /// 输入值超出该函数接受的范围。 OutOfRange, /// An input does not satisfy a documented precondition. + /// + /// 输入不满足文档中说明的前置条件。 InvalidInput, /// An operation is not defined for the given inputs, such as a /// singularity in a coordinate transformation. + /// + /// 对给定输入该运算未定义,例如坐标变换中的奇点。 Undefined, /// A requested item does not exist. + /// + /// 请求的项不存在。 NotFound, /// Two inputs are inconsistent with each other. + /// + /// 两个输入彼此矛盾。 Inconsistent, - /// An operation did not converge within the allowed number of steps. + /// An operation did not converge within the allowed number of steps + /// + /// 该运算未在允许的步数内收敛. NotConverged, /// Text could not be parsed into the expected structure. + /// + /// 文本无法解析为预期的结构。 Parse, /// A numeric operation produced a non-finite result. + /// + /// 某个数值运算产生了非有限结果。 NotFinite, /// A feature or capability is not implemented. + /// + /// 某项特性或能力未实现。 Unsupported, /// A failure that does not fit any other category. + /// + /// 不属于任何其他类别的失败。 Other, } @@ -63,6 +92,11 @@ impl fmt::Display for ErrorKind { /// `Error` carries a machine matchable [`ErrorKind`] plus a human readable /// message. An optional source records the lower level error that caused /// this one. +/// +/// 工作区使用的错误类型。 +/// +/// `Error` 携带可供机器匹配的 [`ErrorKind`] 与一条供人阅读的消息。可选的 +/// source 记录导致该错误的底层错误。 pub struct Error { kind: ErrorKind, message: Option, @@ -71,6 +105,8 @@ pub struct Error { impl Error { /// Creates an error with the given kind and no message. + /// + /// 用给定类别创建一个不带消息的错误。 pub fn new(kind: ErrorKind) -> Self { Self { kind, @@ -81,6 +117,8 @@ impl Error { /// Creates an error with the given kind and message. /// + /// 用给定类别与消息创建一个错误。 + /// /// The message should describe the concrete cause of this specific /// failure. It is meant for humans reading logs, not for matching. pub fn with_message(kind: ErrorKind, message: impl Into) -> Self { @@ -92,6 +130,8 @@ impl Error { } /// Creates an error with the given kind and a source error. + /// + /// 用给定类别与来源错误创建一个错误。 pub fn with_source(kind: ErrorKind, source: E) -> Self where E: StdError + Send + Sync + 'static, @@ -104,12 +144,16 @@ impl Error { } /// Attaches a message to this error, replacing any existing message. + /// + /// 为该错误附加消息,替换已有消息。 pub fn with_context(mut self, message: impl Into) -> Self { self.message = Some(message.into()); self } /// Attaches a source error, replacing any existing source. + /// + /// 为该错误附加来源错误,替换已有来源。 pub fn caused_by(mut self, source: E) -> Self where E: StdError + Send + Sync + 'static, @@ -119,11 +163,15 @@ impl Error { } /// Returns the category of this error. + /// + /// 返回该错误的类别。 pub fn kind(&self) -> ErrorKind { self.kind } /// Returns the message attached to this error, if any. + /// + /// 返回附加在该错误上的消息,若无则返回 `None`。 pub fn message(&self) -> Option<&str> { self.message.as_deref() } @@ -166,5 +214,7 @@ impl From for Error { } } -/// A [`Result`](core::result::Result) whose error type is [`Error`]. +/// A [`Result`](core::result::Result) whose error type is [`Error`] +/// +/// 错误类型为 [`Error`] 的 [`Result`](core::result::Result). pub type Result = core::result::Result; diff --git a/crates/foundation/apsis-core/src/quantity.rs b/crates/foundation/apsis-core/src/quantity.rs index db0748f..16f3366 100644 --- a/crates/foundation/apsis-core/src/quantity.rs +++ b/crates/foundation/apsis-core/src/quantity.rs @@ -9,45 +9,66 @@ //! of its kind are in the corresponding base unit, which allows conversion //! between units of the same dimension. The base unit of a dimension always //! has a factor of one. +// +//! 单位本身是任意实现 [`Unit`] 的类型。单位知道自己相当于多少个对应的基本单位, +//! 从而可以在同一量纲的单位之间换算。某量纲的基本单位其因子恒为一。 use core::fmt; /// A unit of a single physical dimension. /// +/// 单一物理量纲的单位。 +/// /// Implementors are zero sized marker types. Two units that implement /// `Unit` for the same dimension convert through /// [`Quantity::to_unit`](crate::Quantity::to_unit). pub trait Unit: Copy + fmt::Debug + fmt::Display + 'static { /// The physical dimension this unit measures, such as length or time. + /// + /// 该单位度量的物理量纲,例如长度或时间。 type Dimension: fmt::Debug + 'static; /// The number of base units in one of this unit. /// + /// 一个该单位相当于多少个基本单位。 + /// /// For example a kilometer has a factor of 1000 when the base unit is /// the meter. The base unit of a dimension has a factor of exactly one. fn factor_to_base() -> f64; /// The symbol used to print the unit, such as `m` or `s`. + /// + /// 打印该单位所用的符号,例如 `m` 或 `s`。 fn symbol() -> &'static str; } /// A value paired with a unit. /// +/// 与单位配对的一个数值。 +/// /// Implementors are newtypes over `f64` and a [`Unit`]. The trait gives /// generic code access to the stored value and its unit without assuming /// which newtype it was handed. pub trait Quantity: Copy { /// The unit this quantity is expressed in. + /// + /// 该量所表示的单位。 type U: Unit; /// Returns the stored numeric value in this quantity's own unit. + /// + /// 以该量自身单位返回所存储的数值。 fn value(self) -> f64; /// Builds a quantity from a numeric value in this quantity's own unit. + /// + /// 由该量自身单位的数值构造一个量。 fn from_value(value: f64) -> Self; /// Converts this quantity to another unit of the same dimension. /// + /// 把该量换算为同一量纲的另一个单位。 + /// /// The value is scaled by the ratio of the two unit factors. The /// conversion is exact only when the factors are exact powers of ten, /// so callers that need a specific unit for output should convert once diff --git a/crates/foundation/apsis-units/src/angle.rs b/crates/foundation/apsis-units/src/angle.rs index a9f264b..c8661e6 100644 --- a/crates/foundation/apsis-units/src/angle.rs +++ b/crates/foundation/apsis-units/src/angle.rs @@ -8,6 +8,15 @@ //! factor is the irrational constant pi. The factor is derived from //! [`f64::consts::PI`](core::f64::consts::PI) rather than from a rounded //! literal. +//! +//! 平面角量。 +//! +//! 基本单位是弧度。[`Radians`] 与 [`Degrees`] 都度量 +//! [`Angle`](crate::dimension::Angle),并通过 +//! [`Quantity::to_unit`](apsis_core::Quantity::to_unit) 相互换算。 +//! +//! 度并非弧度的十的整数次幂,故换算因子是无理常数 pi。该因子取自 +//! [`f64::consts::PI`](core::f64::consts::PI) 而非某个取整后的字面量。 use crate::dimension::Angle; use crate::macros::impl_quantity_ops; @@ -16,25 +25,35 @@ use core::f64::consts::PI; use core::fmt; /// An angle in radians. +/// +/// 以弧度表示的角度。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Radians(f64); /// An angle in degrees. +/// +/// 以度表示的角度。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Degrees(f64); impl Radians { /// Creates an angle from a value in radians. + /// + /// 由弧度值创建角度。 pub const fn new(value: f64) -> Self { Self(value) } /// Returns this angle in radians. + /// + /// 返回该角度的弧度值。 pub const fn value(self) -> f64 { self.0 } /// Converts this angle to degrees. + /// + /// 把该角度换算为度。 pub fn to_degrees(self) -> Degrees { Degrees(self.0 * 180.0 / PI) } @@ -43,6 +62,11 @@ impl Radians { /// /// Wrapping uses [`f64::rem_euclid`](f64::rem_euclid) so the result is /// well defined for negative inputs as well as large ones. + /// + /// 返回该角度归约到半开区间 `[-pi, pi)` 后的值。 + /// + /// 归约使用 [`f64::rem_euclid`](f64::rem_euclid),因此对负输入与很大输入 + /// 都有良好定义。 pub fn wrap(self) -> Self { let two_pi = 2.0 * PI; let wrapped = (self.0 + PI).rem_euclid(two_pi) - PI; @@ -52,16 +76,22 @@ impl Radians { impl Degrees { /// Creates an angle from a value in degrees. + /// + /// 由角度值创建角度。 pub const fn new(value: f64) -> Self { Self(value) } /// Returns this angle in degrees. + /// + /// 返回该角度的度值。 pub const fn value(self) -> f64 { self.0 } /// Converts this angle to radians. + /// + /// 把该角度换算为弧度。 pub fn to_radians(self) -> Radians { Radians(self.0 * PI / 180.0) } diff --git a/crates/foundation/apsis-units/src/dimension.rs b/crates/foundation/apsis-units/src/dimension.rs index 407a26c..f110661 100644 --- a/crates/foundation/apsis-units/src/dimension.rs +++ b/crates/foundation/apsis-units/src/dimension.rs @@ -4,23 +4,39 @@ //! [`Unit`](apsis_core::Unit) declares which dimension it measures through //! its `Dimension` associated type, and conversion between quantities is //! only permitted when the dimensions match. +//! +//! 物理量纲标记。 +//! +//! 每个标记是一个零大小类型,命名一个物理量纲。[`Unit`](apsis_core::Unit) +//! 通过其 `Dimension` 关联类型声明所度量的量纲,量之间的换算仅在量纲相同时 +//! 才被允许。 /// Length. +/// +/// 长度。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Length; /// Duration. +/// +/// 时间。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Time; /// Frequency, the reciprocal of time. +/// +/// 频率,时间的倒数。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Frequency; /// Plane angle. +/// +/// 平面角。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Angle; /// Thermodynamic temperature. +/// +/// 热力学温度。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Temperature; diff --git a/crates/foundation/apsis-units/src/frequency.rs b/crates/foundation/apsis-units/src/frequency.rs index f914e14..13643ce 100644 --- a/crates/foundation/apsis-units/src/frequency.rs +++ b/crates/foundation/apsis-units/src/frequency.rs @@ -2,6 +2,11 @@ //! //! The base unit is the hertz, one cycle per second. [`Hertz`] measures //! [`Frequency`](crate::dimension::Frequency). +//! +//! 频率量。 +//! +//! 基本单位是赫兹,即每秒一个周期。[`Hertz`] 度量 +//! [`Frequency`](crate::dimension::Frequency)。 use crate::dimension::Frequency; use crate::macros::impl_quantity_ops; @@ -9,16 +14,22 @@ use apsis_core::{Quantity, Unit}; use core::fmt; /// A frequency in hertz. +/// +/// 以赫兹表示的频率。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Hertz(f64); impl Hertz { /// Creates a frequency from a value in hertz. + /// + /// 由赫兹值创建频率。 pub const fn new(value: f64) -> Self { Self(value) } /// Returns this frequency in hertz. + /// + /// 返回该频率的赫兹值。 pub const fn value(self) -> f64 { self.0 } @@ -27,6 +38,10 @@ impl Hertz { /// /// Returns `f64::INFINITY` when the frequency is zero, which is the /// correct limit of an infinite period. + /// + /// 返回与该频率对应的周期,单位为秒。 + /// + /// 频率为零时返回 `f64::INFINITY`,这正是周期趋于无穷的正确极限。 pub fn period(self) -> f64 { 1.0 / self.0 } diff --git a/crates/foundation/apsis-units/src/length.rs b/crates/foundation/apsis-units/src/length.rs index 32d9395..b430524 100644 --- a/crates/foundation/apsis-units/src/length.rs +++ b/crates/foundation/apsis-units/src/length.rs @@ -3,6 +3,12 @@ //! The base unit is the meter. [`Meters`] and [`Kilometers`] both measure //! [`Length`](crate::dimension::Length) and convert into each other through //! [`Quantity::to_unit`](apsis_core::Quantity::to_unit). +//! +//! 长度量。 +//! +//! 基本单位是米。[`Meters`] 与 [`Kilometers`] 都度量 +//! [`Length`](crate::dimension::Length),并通过 +//! [`Quantity::to_unit`](apsis_core::Quantity::to_unit) 相互换算。 use crate::dimension::Length; use crate::macros::impl_quantity_ops; @@ -10,25 +16,35 @@ use apsis_core::{Quantity, Unit}; use core::fmt; /// A length in meters. +/// +/// 以米为单位的长度。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Meters(f64); /// A length in kilometers. +/// +/// 以千米为单位的长度。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Kilometers(f64); impl Meters { /// Creates a length from a value in meters. + /// + /// 由米值创建长度。 pub const fn new(value: f64) -> Self { Self(value) } /// Returns this length in meters. + /// + /// 返回该长度的米值。 pub const fn value(self) -> f64 { self.0 } /// Converts this length to kilometers. + /// + /// 把该长度换算为千米。 pub fn to_kilometers(self) -> Kilometers { Kilometers(self.0 / 1_000.0) } @@ -36,16 +52,22 @@ impl Meters { impl Kilometers { /// Creates a length from a value in kilometers. + /// + /// 由千米值创建长度。 pub const fn new(value: f64) -> Self { Self(value) } /// Returns this length in kilometers. + /// + /// 返回该长度的千米值。 pub const fn value(self) -> f64 { self.0 } /// Converts this length to meters. + /// + /// 把该长度换算为米。 pub fn to_meters(self) -> Meters { Meters(self.0 * 1_000.0) } diff --git a/crates/foundation/apsis-units/src/lib.rs b/crates/foundation/apsis-units/src/lib.rs index 45b46f0..af19df1 100644 --- a/crates/foundation/apsis-units/src/lib.rs +++ b/crates/foundation/apsis-units/src/lib.rs @@ -6,6 +6,12 @@ //! [`Quantity::to_unit`], and each unit also //! offers direct conversions for the common cases. //! +//! 物理量的强类型 newtype。 +//! +//! 每个量都是 `f64` 上的 newtype,并把单位带进类型系统,因此长度无法被传到 +//! 需要时长的位置。同一量纲的单位通过 [`Quantity::to_unit`] 换算,每个单位 +//! 另外为常见情形提供直接换算。 +//! //! A newtype implements both [`Unit`] and [`Quantity`], with its own type as //! the associated unit. For example [`Meters`] is a [`Length`](dimension::Length) //! unit and a quantity whose unit is itself. @@ -15,6 +21,14 @@ //! set, so Celsius is handled by explicit methods on [`Kelvin`] rather than //! by the linear [`Unit`] conversion factor. //! +//! 一个 newtype 同时实现 [`Unit`] 与 [`Quantity`],并以自身类型作为关联单位。 +//! 例如 [`Meters`] 既是一个 [`Length`](dimension::Length) 单位,也是单位为 +//! 自身的量。 +//! +//! 此处覆盖的量纲为长度、时间、频率、角度与热力学温度。温度是集合中唯一的 +//! 仿射标度,因此摄氏度由 [`Kelvin`] 上的显式方法处理,而不用 [`Unit`] 的 +//! 线性换算因子。 +//! //! ``` //! use apsis_core::Quantity; //! use apsis_units::{Kilometers, Meters}; diff --git a/crates/foundation/apsis-units/src/macros.rs b/crates/foundation/apsis-units/src/macros.rs index dbab19c..7abf097 100644 --- a/crates/foundation/apsis-units/src/macros.rs +++ b/crates/foundation/apsis-units/src/macros.rs @@ -4,12 +4,23 @@ //! The operators are mechanical, so they are generated from one definition //! rather than repeated per unit. The macro is crate internal; the crate //! documentation describes the public behaviour. +//! +//! 为量 newtype 生成算术运算符的内部辅助。 +//! +//! 这些运算符是机械的,因此由一处定义生成,而不为每个单位重复。该宏仅在 crate +//! 内部使用;公开行为见 crate 文档。 /// Implements `Add`, `Sub`, `Neg`, `Mul`, `Div`, `Mul for /// f64`, and `Div` for a `f64` backed quantity newtype. /// /// The newtype must be a tuple struct with a single `f64` field visible in /// the invoking module. +/// +/// 为以 `f64` 为内部表示的量 newtype 实现 `Add`、`Sub`、`Neg`、`Mul`、 +/// `Div`、`Mul for f64` 与 `Div`。 +/// +/// 该 newtype 必须是一个具名字段为 `f64` 的元组结构体,且该字段在调用它的 +/// 模块中可见。 macro_rules! impl_quantity_ops { ($quantity:ident) => { impl core::ops::Add for $quantity { diff --git a/crates/foundation/apsis-units/src/temperature.rs b/crates/foundation/apsis-units/src/temperature.rs index 781b514..60ce02a 100644 --- a/crates/foundation/apsis-units/src/temperature.rs +++ b/crates/foundation/apsis-units/src/temperature.rs @@ -8,6 +8,15 @@ //! them requires an offset and cannot be expressed by the linear //! [`Unit`] factor. Celsius is therefore offered //! through explicit methods rather than as a second [`Unit`] type. +//! +//! 热力学温度量。 +//! +//! 基本单位是开尔文。[`Kelvin`] 度量 +//! [`Temperature`](crate::dimension::Temperature)。 +//! +//! 温度是本 crate 中唯一的仿射标度。摄氏标度与开尔文大小相同但零点不同,二者 +//! 换算需要一个偏移,无法用线性的 [`Unit`] 因子表达。因此摄氏度以显式方法 +//! 提供,而不是作为第二个 [`Unit`] 类型。 use crate::dimension::Temperature; use crate::macros::impl_quantity_ops; @@ -16,14 +25,21 @@ use core::fmt; /// A thermodynamic temperature in kelvin. /// +/// 以开尔文表示的热力学温度。 +/// /// Values below absolute zero are not rejected, because intermediate /// arithmetic may legitimately pass through them; callers that require a /// physical temperature should check the value. +/// +/// 不拒绝低于绝对零的值,因为中间运算可能合法地经过该区域;需要物理温度的调用方 +/// 应自行检查。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Kelvin(f64); impl Kelvin { /// Creates a temperature from a value in kelvin. + /// + /// 由开尔文值创建温度。 pub const fn new(value: f64) -> Self { Self(value) } @@ -31,16 +47,24 @@ impl Kelvin { /// Creates a temperature from a value in degrees Celsius. /// /// Uses the exact offset defined by the Celsius scale. + /// + /// 由摄氏度值创建温度。 + /// + /// 使用摄氏标度所定义的精确偏移。 pub fn from_celsius(celsius: f64) -> Self { Self(celsius + apsis_core::constant::ZERO_CELSIUS_IN_KELVIN) } /// Returns this temperature in kelvin. + /// + /// 返回该温度的开尔文值。 pub const fn value(self) -> f64 { self.0 } /// Converts this temperature to degrees Celsius. + /// + /// 把该温度换算为摄氏度。 pub fn to_celsius(self) -> f64 { self.0 - apsis_core::constant::ZERO_CELSIUS_IN_KELVIN } diff --git a/crates/foundation/apsis-units/src/time.rs b/crates/foundation/apsis-units/src/time.rs index 1aa93b8..bf35848 100644 --- a/crates/foundation/apsis-units/src/time.rs +++ b/crates/foundation/apsis-units/src/time.rs @@ -2,6 +2,11 @@ //! //! The base unit is the second. [`Seconds`] is the only unit defined here; //! it measures [`Time`](crate::dimension::Time). +//! +//! 时间量。 +//! +//! 基本单位是秒。[`Seconds`] 是此处唯一定义的单位,度量 +//! [`Time`](crate::dimension::Time)。 use crate::dimension::Time; use crate::macros::impl_quantity_ops; @@ -9,16 +14,22 @@ use apsis_core::{Quantity, Unit}; use core::fmt; /// A duration in seconds. +/// +/// 以秒为单位的时长。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Seconds(f64); impl Seconds { /// Creates a duration from a value in seconds. + /// + /// 由秒值创建时长。 pub const fn new(value: f64) -> Self { Self(value) } /// Returns this duration in seconds. + /// + /// 返回该时长的秒值。 pub const fn value(self) -> f64 { self.0 } diff --git a/scripts/check-bilingual.py b/scripts/check-bilingual.py new file mode 100755 index 0000000..09cd51e --- /dev/null +++ b/scripts/check-bilingual.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Check that every source comment has a Chinese counterpart. + +The repository is being made bilingual: each comment block carries the +English paragraph first and the Chinese one after a blank doc line. This +script reports the blocks that have no Chinese yet, so the sweep can be +driven and verified without reading every file. + +It also reports the two mistakes that are easy to make while adding a +language: a code string that was translated by accident, and a comment that +mentions a term the glossary keeps in English where a Chinese name exists. + +Usage: + + python3 scripts/check-bilingual.py # every crate + python3 scripts/check-bilingual.py crates/orbit + python3 scripts/check-bilingual.py --quiet # only the summary line +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + +# A comment line: a doc line, or a plain comment that is not a doc marker. +DOC = re.compile(r"^\s*///") +LINE = re.compile(r"^\s*//[^/!]") + +# CJK, used to decide whether a block already carries the Chinese text. +CJK = re.compile(r"[\u4e00-\u9fff]") + +# The replacement character, which shows up when a byte sequence is mangled. +MOJIBAKE = "�" + +# An English function word sitting between Chinese characters, which means a +# half translated sentence. The glossary keeps words like `Walker` and +# `SGP4` in English, so a lowercase word between CJK is a mistake. +MIXED = re.compile(r"[\u4e00-\u9fff][a-z]{3,}[\u4e00-\u9fff]") + + +def blocks(path: Path) -> list[tuple[int, list[str]]]: + """Returns the comment blocks of a file with the line each starts on.""" + lines = path.read_text(encoding="utf-8").splitlines() + found: list[tuple[int, list[str]]] = [] + index = 0 + while index < len(lines): + if DOC.match(lines[index]) or LINE.match(lines[index]): + start = index + block: list[str] = [] + while index < len(lines) and (DOC.match(lines[index]) or LINE.match(lines[index])): + block.append(lines[index]) + index += 1 + found.append((start + 1, block)) + else: + index += 1 + return found + + +def main(argv: list[str]) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("paths", nargs="*", default=[]) + parser.add_argument("--quiet", action="store_true") + args = parser.parse_args(argv) + + roots = [Path(p).resolve() for p in args.paths] or [ROOT / "crates"] + files: list[Path] = [] + for base in roots: + if base.is_file(): + files.append(base) + else: + files.extend(sorted(base.rglob("src/**/*.rs"))) + + total = 0 + translated = 0 + gaps: list[str] = [] + mojibake: list[str] = [] + mixed: list[str] = [] + + for path in files: + if "examples" in path.parts: + continue + for line, block in blocks(path): + text = "\n".join(block) + if MOJIBAKE in text: + mojibake.append(f"{path.relative_to(ROOT)}:{line}") + found = MIXED.search(text) + if found: + mixed.append( + f"{path.relative_to(ROOT)}:{line} {found.group(0)!r}" + ) + total += 1 + if CJK.search(text): + translated += 1 + else: + first = block[0].strip().lstrip("/!").strip() + gaps.append(f"{path.relative_to(ROOT)}:{line} {first[:64]}") + + if not args.quiet: + for gap in gaps: + print(f" no Chinese {gap}") + for bad in mojibake: + print(f" MOJIBAKE {bad}") + for bad in mixed: + print(f" MIXED {bad}") + + remaining = len(gaps) + print( + f"comment blocks: {total}, bilingual: {translated}, " + f"still English only: {remaining}" + ) + return 1 if (mojibake or mixed) else 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:]))