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.
This commit is contained in:
ApsisRS contributors 2026-10-02 00:28:22 +08:00
parent fd847a9669
commit d9a934128a
12 changed files with 421 additions and 2 deletions

View File

@ -19,10 +19,17 @@
//! Where a value is defined exactly by a standard, the doc comment says so, //! 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 //! because several derived quantities depend on the exact value rather than
//! the rounded printed one. //! the rounded printed one.
//!
//! 当某个数值由标准精确定义时,文档注释会指出这一点,因为若干导出量依赖精确值
//! 而非取整后的打印值。
/// Speed of light in vacuum, in meters per second. /// Speed of light in vacuum, in meters per second.
/// ///
/// Exact by definition of the metre in the SI. /// Exact by definition of the metre in the SI.
///
/// 真空光速,单位为米每秒。
///
/// 由 SI 对米的定义精确确定。
pub const SPEED_OF_LIGHT: f64 = 299_792_458.0; pub const SPEED_OF_LIGHT: f64 = 299_792_458.0;
/// Newtonian constant of gravitation, in cubic meters per kilogram per /// 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 /// CODATA 2018 value with its standard uncertainty of
/// `1.5e-11 m^3 kg^-1 s^-2`. /// `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; pub const GRAVITATIONAL_CONSTANT: f64 = 6.674_30e-11;
/// Boltzmann constant, in joules per kelvin. /// Boltzmann constant, in joules per kelvin.
/// ///
/// Exact by definition of the kelvin in the SI. /// Exact by definition of the kelvin in the SI.
///
/// 玻尔兹曼常量,单位为焦耳每开尔文。
///
/// 由 SI 对开尔文的定义精确确定。
pub const BOLTZMANN: f64 = 1.380_649e-23; pub const BOLTZMANN: f64 = 1.380_649e-23;
/// Planck constant, in joule seconds. /// Planck constant, in joule seconds.
/// ///
/// Exact by definition of the kilogram in the SI. /// Exact by definition of the kilogram in the SI.
///
/// 普朗克常量,单位为焦耳秒。
///
/// 由 SI 对千克的定义精确确定。
pub const PLANCK: f64 = 6.626_070_15e-34; pub const PLANCK: f64 = 6.626_070_15e-34;
/// Elementary charge, in coulombs. /// Elementary charge, in coulombs.
/// ///
/// Exact by definition of the ampere in the SI. /// Exact by definition of the ampere in the SI.
///
/// elementary charge,单位为库仑。
///
/// 由 SI 对安培的定义精确确定。
pub const ELEMENTARY_CHARGE: f64 = 1.602_176_634e-19; pub const ELEMENTARY_CHARGE: f64 = 1.602_176_634e-19;
/// Astronomical unit, in meters. /// Astronomical unit, in meters.
/// ///
/// Exact by IAU 2012 Resolution B2. /// Exact by IAU 2012 Resolution B2.
///
/// 天文单位,单位为米。
///
/// 由 IAU 2012 Resolution B2 精确确定。
pub const ASTRONOMICAL_UNIT: f64 = 149_597_870_700.0; pub const ASTRONOMICAL_UNIT: f64 = 149_597_870_700.0;
/// Standard gravity, in meters per second squared. /// Standard gravity, in meters per second squared.
/// ///
/// Exact by definition of the standard atmosphere. /// Exact by definition of the standard atmosphere.
///
/// 标准重力加速度,单位为米每二次方秒。
///
/// 由标准大气的定义精确确定。
pub const STANDARD_GRAVITY: f64 = 9.806_65; pub const STANDARD_GRAVITY: f64 = 9.806_65;
/// One minute, in seconds. /// One minute, in seconds.
///
/// 一分钟,单位为秒。
pub const MINUTE: f64 = 60.0; pub const MINUTE: f64 = 60.0;
/// One hour, in seconds. /// One hour, in seconds.
///
/// 一小时,单位为秒。
pub const HOUR: f64 = 3_600.0; pub const HOUR: f64 = 3_600.0;
/// One day of 24 hours, in seconds. /// 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 /// 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 /// longer, and not the Julian day of 86 400 seconds used in Julian date
/// arithmetic, which is identical to this value. /// arithmetic, which is identical to this value.
///
/// 一个 24 小时的日,单位为秒。
///
/// 这是 SI 日。它不是略长的平均太阳日,也不是儒略日运算中使用的 86 400 秒
/// 儒略日,后者与本值相同。
pub const DAY: f64 = 86_400.0; pub const DAY: f64 = 86_400.0;
/// One Julian year of 365.25 days, in seconds. /// One Julian year of 365.25 days, in seconds.
/// ///
/// Exact by definition of the Julian year. /// Exact by definition of the Julian year.
///
/// 一个 365.25 日的儒略年,单位为秒。
///
/// 由儒略年的定义精确确定。
pub const JULIAN_YEAR: f64 = 31_557_600.0; pub const JULIAN_YEAR: f64 = 31_557_600.0;
/// One Julian century of 36 525 days, in seconds. /// One Julian century of 36 525 days, in seconds.
/// ///
/// Exact by definition of the Julian year, used by precession and secular /// Exact by definition of the Julian year, used by precession and secular
/// rate expressions quoted per Julian century. /// rate expressions quoted per Julian century.
///
/// 一个 36 525 日的儒略世纪,单位为秒。
///
/// 由儒略年的定义精确确定;岁差与长期变化率表达式按儒略世纪引用时使用它。
pub const JULIAN_CENTURY: f64 = 3_155_760_000.0; pub const JULIAN_CENTURY: f64 = 3_155_760_000.0;
/// Julian date of the Unix epoch, 1970-01-01T00:00:00 UTC. /// Julian date of the Unix epoch, 1970-01-01T00:00:00 UTC.
/// ///
/// This is `2 440 587.5` in the Julian date system. /// 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; pub const JULIAN_DATE_UNIX_EPOCH: f64 = 2_440_587.5;
/// Julian date of the J2000.0 epoch, 2000-01-01T12:00:00 TT. /// Julian date of the J2000.0 epoch, 2000-01-01T12:00:00 TT.
/// ///
/// Exact by definition of the J2000.0 epoch. /// 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; pub const JULIAN_DATE_J2000: f64 = 2_451_545.0;
/// The number of seconds between the J2000.0 epoch and the Unix epoch. /// The number of seconds between the J2000.0 epoch and the Unix epoch.
/// ///
/// Derived from [`JULIAN_DATE_J2000`] and [`JULIAN_DATE_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; pub const SECONDS_J2000_TO_UNIX: f64 = (JULIAN_DATE_J2000 - JULIAN_DATE_UNIX_EPOCH) * DAY;
/// Earth gravitational parameter, in cubic kilometers per second squared. /// 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. /// dominated by the uncertainty in the Newtonian constant of gravitation.
/// Orbit propagation uses kilometers, so the value is given in those units /// Orbit propagation uses kilometers, so the value is given in those units
/// rather than in SI. /// rather than in SI.
///
/// 地球引力参数,单位为立方千米每二次方秒。
///
/// WGS 84 取值 `GM = 398 600.4418 km^3/s^2`,其不确定度主要来自牛顿万有引力常数的
/// 不确定度。轨道传播使用千米,故该值以千米给出而非 SI 单位。
pub const EARTH_MU: f64 = 398_600.441_8; pub const EARTH_MU: f64 = 398_600.441_8;
/// Earth equatorial radius, in kilometers. /// Earth equatorial radius, in kilometers.
/// ///
/// WGS 84 semi-major axis, `a = 6 378.137 km`. /// 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; pub const EARTH_EQUATORIAL_RADIUS: f64 = 6_378.137;
/// Earth flattening, dimensionless. /// Earth flattening, dimensionless.
/// ///
/// WGS 84 value, `f = 1 / 298.257 223 563`. /// 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; pub const EARTH_FLATTENING: f64 = 1.0 / 298.257_223_563;
/// Earth rotation rate, in radians per second. /// Earth rotation rate, in radians per second.
/// ///
/// WGS 84 value. This is the nominal mean angular velocity of the Earth, /// WGS 84 value. This is the nominal mean angular velocity of the Earth,
/// `omega = 7.292 115e-5 rad/s`. /// `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; pub const EARTH_ROTATION_RATE: f64 = 7.292_115_0e-5;
/// Earth second zonal harmonic, dimensionless. /// Earth second zonal harmonic, dimensionless.
/// ///
/// WGS 84 value, `J2 = 1.082 626 683e-3`. /// 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; pub const EARTH_J2: f64 = 1.082_626_683e-3;
/// Standard atmosphere sea level pressure, in pascals. /// Standard atmosphere sea level pressure, in pascals.
/// ///
/// Exact by definition of the standard atmosphere. /// Exact by definition of the standard atmosphere.
///
/// 标准大气海平面气压,单位为帕斯卡。
///
/// 由标准大气的定义精确确定。
pub const STANDARD_ATMOSPHERE: f64 = 101_325.0; pub const STANDARD_ATMOSPHERE: f64 = 101_325.0;
/// Zero degrees Celsius on the thermodynamic temperature scale, in kelvin. /// Zero degrees Celsius on the thermodynamic temperature scale, in kelvin.
/// ///
/// Exact by definition of the Celsius scale. /// Exact by definition of the Celsius scale.
///
/// 热力学温标上零摄氏度对应的温度,单位为开尔文。
///
/// 由摄氏标度的定义精确确定。
pub const ZERO_CELSIUS_IN_KELVIN: f64 = 273.15; pub const ZERO_CELSIUS_IN_KELVIN: f64 = 273.15;

View File

@ -4,6 +4,10 @@
//! as a single [`Error`] struct carrying an [`ErrorKind`], an optional //! as a single [`Error`] struct carrying an [`ErrorKind`], an optional
//! message, and an optional source. `ErrorKind` is a fieldless enum so that //! message, and an optional source. `ErrorKind` is a fieldless enum so that
//! callers can match on the failure category without allocating. //! callers can match on the failure category without allocating.
//!
//! 工作区不使用外部错误 crate。错误统一建模为单个 [`Error`] 结构体,携带
//! [`ErrorKind`]、可选消息与可选来源。`ErrorKind` 是无字段枚举,因此调用方可以
//! 在不分配内存的前提下匹配失败类别。
use core::error::Error as StdError; use core::error::Error as StdError;
use core::fmt; use core::fmt;
@ -14,29 +18,54 @@ use core::fmt;
/// any layer may return any variant. Callers should match on the variant /// 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 /// that is relevant to their own recovery logic and treat the rest as
/// opaque. /// opaque.
///
/// [`Error`] 的类别。
///
/// 各变体按最常产生它们的层分组,但任何层都可以返回任何变体。调用方应只匹配
/// 与自身恢复逻辑相关的变体,其余视为不透明。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive] #[non_exhaustive]
pub enum ErrorKind { pub enum ErrorKind {
/// An input value is outside the range the function accepts. /// An input value is outside the range the function accepts.
///
/// 输入值超出该函数接受的范围。
OutOfRange, OutOfRange,
/// An input does not satisfy a documented precondition. /// An input does not satisfy a documented precondition.
///
/// 输入不满足文档中说明的前置条件。
InvalidInput, InvalidInput,
/// An operation is not defined for the given inputs, such as a /// An operation is not defined for the given inputs, such as a
/// singularity in a coordinate transformation. /// singularity in a coordinate transformation.
///
/// 对给定输入该运算未定义,例如坐标变换中的奇点。
Undefined, Undefined,
/// A requested item does not exist. /// A requested item does not exist.
///
/// 请求的项不存在。
NotFound, NotFound,
/// Two inputs are inconsistent with each other. /// Two inputs are inconsistent with each other.
///
/// 两个输入彼此矛盾。
Inconsistent, Inconsistent,
/// An operation did not converge within the allowed number of steps. /// An operation did not converge within the allowed number of steps
///
/// 该运算未在允许的步数内收敛.
NotConverged, NotConverged,
/// Text could not be parsed into the expected structure. /// Text could not be parsed into the expected structure.
///
/// 文本无法解析为预期的结构。
Parse, Parse,
/// A numeric operation produced a non-finite result. /// A numeric operation produced a non-finite result.
///
/// 某个数值运算产生了非有限结果。
NotFinite, NotFinite,
/// A feature or capability is not implemented. /// A feature or capability is not implemented.
///
/// 某项特性或能力未实现。
Unsupported, Unsupported,
/// A failure that does not fit any other category. /// A failure that does not fit any other category.
///
/// 不属于任何其他类别的失败。
Other, Other,
} }
@ -63,6 +92,11 @@ impl fmt::Display for ErrorKind {
/// `Error` carries a machine matchable [`ErrorKind`] plus a human readable /// `Error` carries a machine matchable [`ErrorKind`] plus a human readable
/// message. An optional source records the lower level error that caused /// message. An optional source records the lower level error that caused
/// this one. /// this one.
///
/// 工作区使用的错误类型。
///
/// `Error` 携带可供机器匹配的 [`ErrorKind`] 与一条供人阅读的消息。可选的
/// source 记录导致该错误的底层错误。
pub struct Error { pub struct Error {
kind: ErrorKind, kind: ErrorKind,
message: Option<String>, message: Option<String>,
@ -71,6 +105,8 @@ pub struct Error {
impl Error { impl Error {
/// Creates an error with the given kind and no message. /// Creates an error with the given kind and no message.
///
/// 用给定类别创建一个不带消息的错误。
pub fn new(kind: ErrorKind) -> Self { pub fn new(kind: ErrorKind) -> Self {
Self { Self {
kind, kind,
@ -81,6 +117,8 @@ impl Error {
/// Creates an error with the given kind and message. /// Creates an error with the given kind and message.
/// ///
/// 用给定类别与消息创建一个错误。
///
/// The message should describe the concrete cause of this specific /// The message should describe the concrete cause of this specific
/// failure. It is meant for humans reading logs, not for matching. /// failure. It is meant for humans reading logs, not for matching.
pub fn with_message(kind: ErrorKind, message: impl Into<String>) -> Self { pub fn with_message(kind: ErrorKind, message: impl Into<String>) -> Self {
@ -92,6 +130,8 @@ impl Error {
} }
/// Creates an error with the given kind and a source error. /// Creates an error with the given kind and a source error.
///
/// 用给定类别与来源错误创建一个错误。
pub fn with_source<E>(kind: ErrorKind, source: E) -> Self pub fn with_source<E>(kind: ErrorKind, source: E) -> Self
where where
E: StdError + Send + Sync + 'static, E: StdError + Send + Sync + 'static,
@ -104,12 +144,16 @@ impl Error {
} }
/// Attaches a message to this error, replacing any existing message. /// Attaches a message to this error, replacing any existing message.
///
/// 为该错误附加消息,替换已有消息。
pub fn with_context(mut self, message: impl Into<String>) -> Self { pub fn with_context(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into()); self.message = Some(message.into());
self self
} }
/// Attaches a source error, replacing any existing source. /// Attaches a source error, replacing any existing source.
///
/// 为该错误附加来源错误,替换已有来源。
pub fn caused_by<E>(mut self, source: E) -> Self pub fn caused_by<E>(mut self, source: E) -> Self
where where
E: StdError + Send + Sync + 'static, E: StdError + Send + Sync + 'static,
@ -119,11 +163,15 @@ impl Error {
} }
/// Returns the category of this error. /// Returns the category of this error.
///
/// 返回该错误的类别。
pub fn kind(&self) -> ErrorKind { pub fn kind(&self) -> ErrorKind {
self.kind self.kind
} }
/// Returns the message attached to this error, if any. /// Returns the message attached to this error, if any.
///
/// 返回附加在该错误上的消息,若无则返回 `None`。
pub fn message(&self) -> Option<&str> { pub fn message(&self) -> Option<&str> {
self.message.as_deref() self.message.as_deref()
} }
@ -166,5 +214,7 @@ impl From<ErrorKind> 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<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;

View File

@ -9,45 +9,66 @@
//! of its kind are in the corresponding base unit, which allows conversion //! 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 //! between units of the same dimension. The base unit of a dimension always
//! has a factor of one. //! has a factor of one.
//
//! 单位本身是任意实现 [`Unit`] 的类型。单位知道自己相当于多少个对应的基本单位,
//! 从而可以在同一量纲的单位之间换算。某量纲的基本单位其因子恒为一。
use core::fmt; use core::fmt;
/// A unit of a single physical dimension. /// A unit of a single physical dimension.
/// ///
/// 单一物理量纲的单位。
///
/// Implementors are zero sized marker types. Two units that implement /// Implementors are zero sized marker types. Two units that implement
/// `Unit` for the same dimension convert through /// `Unit` for the same dimension convert through
/// [`Quantity::to_unit`](crate::Quantity::to_unit). /// [`Quantity::to_unit`](crate::Quantity::to_unit).
pub trait Unit: Copy + fmt::Debug + fmt::Display + 'static { pub trait Unit: Copy + fmt::Debug + fmt::Display + 'static {
/// The physical dimension this unit measures, such as length or time. /// The physical dimension this unit measures, such as length or time.
///
/// 该单位度量的物理量纲,例如长度或时间。
type Dimension: fmt::Debug + 'static; type Dimension: fmt::Debug + 'static;
/// The number of base units in one of this unit. /// The number of base units in one of this unit.
/// ///
/// 一个该单位相当于多少个基本单位。
///
/// For example a kilometer has a factor of 1000 when the base unit is /// 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. /// the meter. The base unit of a dimension has a factor of exactly one.
fn factor_to_base() -> f64; fn factor_to_base() -> f64;
/// The symbol used to print the unit, such as `m` or `s`. /// The symbol used to print the unit, such as `m` or `s`.
///
/// 打印该单位所用的符号,例如 `m` 或 `s`。
fn symbol() -> &'static str; fn symbol() -> &'static str;
} }
/// A value paired with a unit. /// A value paired with a unit.
/// ///
/// 与单位配对的一个数值。
///
/// Implementors are newtypes over `f64` and a [`Unit`]. The trait gives /// Implementors are newtypes over `f64` and a [`Unit`]. The trait gives
/// generic code access to the stored value and its unit without assuming /// generic code access to the stored value and its unit without assuming
/// which newtype it was handed. /// which newtype it was handed.
pub trait Quantity: Copy { pub trait Quantity: Copy {
/// The unit this quantity is expressed in. /// The unit this quantity is expressed in.
///
/// 该量所表示的单位。
type U: Unit; type U: Unit;
/// Returns the stored numeric value in this quantity's own unit. /// Returns the stored numeric value in this quantity's own unit.
///
/// 以该量自身单位返回所存储的数值。
fn value(self) -> f64; fn value(self) -> f64;
/// Builds a quantity from a numeric value in this quantity's own unit. /// Builds a quantity from a numeric value in this quantity's own unit.
///
/// 由该量自身单位的数值构造一个量。
fn from_value(value: f64) -> Self; fn from_value(value: f64) -> Self;
/// Converts this quantity to another unit of the same dimension. /// Converts this quantity to another unit of the same dimension.
/// ///
/// 把该量换算为同一量纲的另一个单位。
///
/// The value is scaled by the ratio of the two unit factors. The /// 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, /// conversion is exact only when the factors are exact powers of ten,
/// so callers that need a specific unit for output should convert once /// so callers that need a specific unit for output should convert once

View File

@ -8,6 +8,15 @@
//! factor is the irrational constant pi. The factor is derived from //! factor is the irrational constant pi. The factor is derived from
//! [`f64::consts::PI`](core::f64::consts::PI) rather than from a rounded //! [`f64::consts::PI`](core::f64::consts::PI) rather than from a rounded
//! literal. //! 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::dimension::Angle;
use crate::macros::impl_quantity_ops; use crate::macros::impl_quantity_ops;
@ -16,25 +25,35 @@ use core::f64::consts::PI;
use core::fmt; use core::fmt;
/// An angle in radians. /// An angle in radians.
///
/// 以弧度表示的角度。
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Radians(f64); pub struct Radians(f64);
/// An angle in degrees. /// An angle in degrees.
///
/// 以度表示的角度。
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Degrees(f64); pub struct Degrees(f64);
impl Radians { impl Radians {
/// Creates an angle from a value in radians. /// Creates an angle from a value in radians.
///
/// 由弧度值创建角度。
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self(value) Self(value)
} }
/// Returns this angle in radians. /// Returns this angle in radians.
///
/// 返回该角度的弧度值。
pub const fn value(self) -> f64 { pub const fn value(self) -> f64 {
self.0 self.0
} }
/// Converts this angle to degrees. /// Converts this angle to degrees.
///
/// 把该角度换算为度。
pub fn to_degrees(self) -> Degrees { pub fn to_degrees(self) -> Degrees {
Degrees(self.0 * 180.0 / PI) Degrees(self.0 * 180.0 / PI)
} }
@ -43,6 +62,11 @@ impl Radians {
/// ///
/// Wrapping uses [`f64::rem_euclid`](f64::rem_euclid) so the result is /// Wrapping uses [`f64::rem_euclid`](f64::rem_euclid) so the result is
/// well defined for negative inputs as well as large ones. /// well defined for negative inputs as well as large ones.
///
/// 返回该角度归约到半开区间 `[-pi, pi)` 后的值。
///
/// 归约使用 [`f64::rem_euclid`](f64::rem_euclid),因此对负输入与很大输入
/// 都有良好定义。
pub fn wrap(self) -> Self { pub fn wrap(self) -> Self {
let two_pi = 2.0 * PI; let two_pi = 2.0 * PI;
let wrapped = (self.0 + PI).rem_euclid(two_pi) - PI; let wrapped = (self.0 + PI).rem_euclid(two_pi) - PI;
@ -52,16 +76,22 @@ impl Radians {
impl Degrees { impl Degrees {
/// Creates an angle from a value in degrees. /// Creates an angle from a value in degrees.
///
/// 由角度值创建角度。
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self(value) Self(value)
} }
/// Returns this angle in degrees. /// Returns this angle in degrees.
///
/// 返回该角度的度值。
pub const fn value(self) -> f64 { pub const fn value(self) -> f64 {
self.0 self.0
} }
/// Converts this angle to radians. /// Converts this angle to radians.
///
/// 把该角度换算为弧度。
pub fn to_radians(self) -> Radians { pub fn to_radians(self) -> Radians {
Radians(self.0 * PI / 180.0) Radians(self.0 * PI / 180.0)
} }

View File

@ -4,23 +4,39 @@
//! [`Unit`](apsis_core::Unit) declares which dimension it measures through //! [`Unit`](apsis_core::Unit) declares which dimension it measures through
//! its `Dimension` associated type, and conversion between quantities is //! its `Dimension` associated type, and conversion between quantities is
//! only permitted when the dimensions match. //! only permitted when the dimensions match.
//!
//! 物理量纲标记。
//!
//! 每个标记是一个零大小类型,命名一个物理量纲。[`Unit`](apsis_core::Unit)
//! 通过其 `Dimension` 关联类型声明所度量的量纲,量之间的换算仅在量纲相同时
//! 才被允许。
/// Length. /// Length.
///
/// 长度。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Length; pub struct Length;
/// Duration. /// Duration.
///
/// 时间。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Time; pub struct Time;
/// Frequency, the reciprocal of time. /// Frequency, the reciprocal of time.
///
/// 频率,时间的倒数。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Frequency; pub struct Frequency;
/// Plane angle. /// Plane angle.
///
/// 平面角。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Angle; pub struct Angle;
/// Thermodynamic temperature. /// Thermodynamic temperature.
///
/// 热力学温度。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Temperature; pub struct Temperature;

View File

@ -2,6 +2,11 @@
//! //!
//! The base unit is the hertz, one cycle per second. [`Hertz`] measures //! The base unit is the hertz, one cycle per second. [`Hertz`] measures
//! [`Frequency`](crate::dimension::Frequency). //! [`Frequency`](crate::dimension::Frequency).
//!
//! 频率量。
//!
//! 基本单位是赫兹,即每秒一个周期。[`Hertz`] 度量
//! [`Frequency`](crate::dimension::Frequency)。
use crate::dimension::Frequency; use crate::dimension::Frequency;
use crate::macros::impl_quantity_ops; use crate::macros::impl_quantity_ops;
@ -9,16 +14,22 @@ use apsis_core::{Quantity, Unit};
use core::fmt; use core::fmt;
/// A frequency in hertz. /// A frequency in hertz.
///
/// 以赫兹表示的频率。
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Hertz(f64); pub struct Hertz(f64);
impl Hertz { impl Hertz {
/// Creates a frequency from a value in hertz. /// Creates a frequency from a value in hertz.
///
/// 由赫兹值创建频率。
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self(value) Self(value)
} }
/// Returns this frequency in hertz. /// Returns this frequency in hertz.
///
/// 返回该频率的赫兹值。
pub const fn value(self) -> f64 { pub const fn value(self) -> f64 {
self.0 self.0
} }
@ -27,6 +38,10 @@ impl Hertz {
/// ///
/// Returns `f64::INFINITY` when the frequency is zero, which is the /// Returns `f64::INFINITY` when the frequency is zero, which is the
/// correct limit of an infinite period. /// correct limit of an infinite period.
///
/// 返回与该频率对应的周期,单位为秒。
///
/// 频率为零时返回 `f64::INFINITY`,这正是周期趋于无穷的正确极限。
pub fn period(self) -> f64 { pub fn period(self) -> f64 {
1.0 / self.0 1.0 / self.0
} }

View File

@ -3,6 +3,12 @@
//! The base unit is the meter. [`Meters`] and [`Kilometers`] both measure //! The base unit is the meter. [`Meters`] and [`Kilometers`] both measure
//! [`Length`](crate::dimension::Length) and convert into each other through //! [`Length`](crate::dimension::Length) and convert into each other through
//! [`Quantity::to_unit`](apsis_core::Quantity::to_unit). //! [`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::dimension::Length;
use crate::macros::impl_quantity_ops; use crate::macros::impl_quantity_ops;
@ -10,25 +16,35 @@ use apsis_core::{Quantity, Unit};
use core::fmt; use core::fmt;
/// A length in meters. /// A length in meters.
///
/// 以米为单位的长度。
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Meters(f64); pub struct Meters(f64);
/// A length in kilometers. /// A length in kilometers.
///
/// 以千米为单位的长度。
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Kilometers(f64); pub struct Kilometers(f64);
impl Meters { impl Meters {
/// Creates a length from a value in meters. /// Creates a length from a value in meters.
///
/// 由米值创建长度。
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self(value) Self(value)
} }
/// Returns this length in meters. /// Returns this length in meters.
///
/// 返回该长度的米值。
pub const fn value(self) -> f64 { pub const fn value(self) -> f64 {
self.0 self.0
} }
/// Converts this length to kilometers. /// Converts this length to kilometers.
///
/// 把该长度换算为千米。
pub fn to_kilometers(self) -> Kilometers { pub fn to_kilometers(self) -> Kilometers {
Kilometers(self.0 / 1_000.0) Kilometers(self.0 / 1_000.0)
} }
@ -36,16 +52,22 @@ impl Meters {
impl Kilometers { impl Kilometers {
/// Creates a length from a value in kilometers. /// Creates a length from a value in kilometers.
///
/// 由千米值创建长度。
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self(value) Self(value)
} }
/// Returns this length in kilometers. /// Returns this length in kilometers.
///
/// 返回该长度的千米值。
pub const fn value(self) -> f64 { pub const fn value(self) -> f64 {
self.0 self.0
} }
/// Converts this length to meters. /// Converts this length to meters.
///
/// 把该长度换算为米。
pub fn to_meters(self) -> Meters { pub fn to_meters(self) -> Meters {
Meters(self.0 * 1_000.0) Meters(self.0 * 1_000.0)
} }

View File

@ -6,6 +6,12 @@
//! [`Quantity::to_unit`], and each unit also //! [`Quantity::to_unit`], and each unit also
//! offers direct conversions for the common cases. //! 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 //! A newtype implements both [`Unit`] and [`Quantity`], with its own type as
//! the associated unit. For example [`Meters`] is a [`Length`](dimension::Length) //! the associated unit. For example [`Meters`] is a [`Length`](dimension::Length)
//! unit and a quantity whose unit is itself. //! unit and a quantity whose unit is itself.
@ -15,6 +21,14 @@
//! set, so Celsius is handled by explicit methods on [`Kelvin`] rather than //! set, so Celsius is handled by explicit methods on [`Kelvin`] rather than
//! by the linear [`Unit`] conversion factor. //! by the linear [`Unit`] conversion factor.
//! //!
//! 一个 newtype 同时实现 [`Unit`] 与 [`Quantity`],并以自身类型作为关联单位。
//! 例如 [`Meters`] 既是一个 [`Length`](dimension::Length) 单位,也是单位为
//! 自身的量。
//!
//! 此处覆盖的量纲为长度、时间、频率、角度与热力学温度。温度是集合中唯一的
//! 仿射标度,因此摄氏度由 [`Kelvin`] 上的显式方法处理,而不用 [`Unit`] 的
//! 线性换算因子。
//!
//! ``` //! ```
//! use apsis_core::Quantity; //! use apsis_core::Quantity;
//! use apsis_units::{Kilometers, Meters}; //! use apsis_units::{Kilometers, Meters};

View File

@ -4,12 +4,23 @@
//! The operators are mechanical, so they are generated from one definition //! The operators are mechanical, so they are generated from one definition
//! rather than repeated per unit. The macro is crate internal; the crate //! rather than repeated per unit. The macro is crate internal; the crate
//! documentation describes the public behaviour. //! documentation describes the public behaviour.
//!
//! 为量 newtype 生成算术运算符的内部辅助。
//!
//! 这些运算符是机械的,因此由一处定义生成,而不为每个单位重复。该宏仅在 crate
//! 内部使用;公开行为见 crate 文档。
/// Implements `Add`, `Sub`, `Neg`, `Mul<f64>`, `Div<f64>`, `Mul<Self> for /// Implements `Add`, `Sub`, `Neg`, `Mul<f64>`, `Div<f64>`, `Mul<Self> for
/// f64`, and `Div<Self>` for a `f64` backed quantity newtype. /// f64`, and `Div<Self>` for a `f64` backed quantity newtype.
/// ///
/// The newtype must be a tuple struct with a single `f64` field visible in /// The newtype must be a tuple struct with a single `f64` field visible in
/// the invoking module. /// the invoking module.
///
/// 为以 `f64` 为内部表示的量 newtype 实现 `Add`、`Sub`、`Neg`、`Mul<f64>`、
/// `Div<f64>`、`Mul<Self> for f64` 与 `Div<Self>`。
///
/// 该 newtype 必须是一个具名字段为 `f64` 的元组结构体,且该字段在调用它的
/// 模块中可见。
macro_rules! impl_quantity_ops { macro_rules! impl_quantity_ops {
($quantity:ident) => { ($quantity:ident) => {
impl core::ops::Add for $quantity { impl core::ops::Add for $quantity {

View File

@ -8,6 +8,15 @@
//! them requires an offset and cannot be expressed by the linear //! them requires an offset and cannot be expressed by the linear
//! [`Unit`] factor. Celsius is therefore offered //! [`Unit`] factor. Celsius is therefore offered
//! through explicit methods rather than as a second [`Unit`] type. //! through explicit methods rather than as a second [`Unit`] type.
//!
//! 热力学温度量。
//!
//! 基本单位是开尔文。[`Kelvin`] 度量
//! [`Temperature`](crate::dimension::Temperature)。
//!
//! 温度是本 crate 中唯一的仿射标度。摄氏标度与开尔文大小相同但零点不同,二者
//! 换算需要一个偏移,无法用线性的 [`Unit`] 因子表达。因此摄氏度以显式方法
//! 提供,而不是作为第二个 [`Unit`] 类型。
use crate::dimension::Temperature; use crate::dimension::Temperature;
use crate::macros::impl_quantity_ops; use crate::macros::impl_quantity_ops;
@ -16,14 +25,21 @@ use core::fmt;
/// A thermodynamic temperature in kelvin. /// A thermodynamic temperature in kelvin.
/// ///
/// 以开尔文表示的热力学温度。
///
/// Values below absolute zero are not rejected, because intermediate /// Values below absolute zero are not rejected, because intermediate
/// arithmetic may legitimately pass through them; callers that require a /// arithmetic may legitimately pass through them; callers that require a
/// physical temperature should check the value. /// physical temperature should check the value.
///
/// 不拒绝低于绝对零的值,因为中间运算可能合法地经过该区域;需要物理温度的调用方
/// 应自行检查。
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Kelvin(f64); pub struct Kelvin(f64);
impl Kelvin { impl Kelvin {
/// Creates a temperature from a value in kelvin. /// Creates a temperature from a value in kelvin.
///
/// 由开尔文值创建温度。
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self(value) Self(value)
} }
@ -31,16 +47,24 @@ impl Kelvin {
/// Creates a temperature from a value in degrees Celsius. /// Creates a temperature from a value in degrees Celsius.
/// ///
/// Uses the exact offset defined by the Celsius scale. /// Uses the exact offset defined by the Celsius scale.
///
/// 由摄氏度值创建温度。
///
/// 使用摄氏标度所定义的精确偏移。
pub fn from_celsius(celsius: f64) -> Self { pub fn from_celsius(celsius: f64) -> Self {
Self(celsius + apsis_core::constant::ZERO_CELSIUS_IN_KELVIN) Self(celsius + apsis_core::constant::ZERO_CELSIUS_IN_KELVIN)
} }
/// Returns this temperature in kelvin. /// Returns this temperature in kelvin.
///
/// 返回该温度的开尔文值。
pub const fn value(self) -> f64 { pub const fn value(self) -> f64 {
self.0 self.0
} }
/// Converts this temperature to degrees Celsius. /// Converts this temperature to degrees Celsius.
///
/// 把该温度换算为摄氏度。
pub fn to_celsius(self) -> f64 { pub fn to_celsius(self) -> f64 {
self.0 - apsis_core::constant::ZERO_CELSIUS_IN_KELVIN self.0 - apsis_core::constant::ZERO_CELSIUS_IN_KELVIN
} }

View File

@ -2,6 +2,11 @@
//! //!
//! The base unit is the second. [`Seconds`] is the only unit defined here; //! The base unit is the second. [`Seconds`] is the only unit defined here;
//! it measures [`Time`](crate::dimension::Time). //! it measures [`Time`](crate::dimension::Time).
//!
//! 时间量。
//!
//! 基本单位是秒。[`Seconds`] 是此处唯一定义的单位,度量
//! [`Time`](crate::dimension::Time)。
use crate::dimension::Time; use crate::dimension::Time;
use crate::macros::impl_quantity_ops; use crate::macros::impl_quantity_ops;
@ -9,16 +14,22 @@ use apsis_core::{Quantity, Unit};
use core::fmt; use core::fmt;
/// A duration in seconds. /// A duration in seconds.
///
/// 以秒为单位的时长。
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Seconds(f64); pub struct Seconds(f64);
impl Seconds { impl Seconds {
/// Creates a duration from a value in seconds. /// Creates a duration from a value in seconds.
///
/// 由秒值创建时长。
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self(value) Self(value)
} }
/// Returns this duration in seconds. /// Returns this duration in seconds.
///
/// 返回该时长的秒值。
pub const fn value(self) -> f64 { pub const fn value(self) -> f64 {
self.0 self.0
} }

119
scripts/check-bilingual.py Executable file
View File

@ -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 = "<EFBFBD>"
# 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:]))