296 lines
12 KiB
Rust
296 lines
12 KiB
Rust
//! Chinese labels for the tool results.
|
||
//!
|
||
//! A tool handler writes its labels in English, because English is the
|
||
//! language the interface identifiers and the `apsis-rs` documentation use.
|
||
//! This table pairs each label with its Chinese form so that a result can be
|
||
//! shown in either language without the handler carrying two copies of every
|
||
//! string.
|
||
//!
|
||
//! A label that is absent from the table is shown in English in both
|
||
//! languages. That is the correct behaviour for a value that is a symbol
|
||
//! rather than prose, such as `position.x`, `BSTAR`, or a unit name.
|
||
|
||
use crate::i18n::Text;
|
||
|
||
/// Returns the bilingual form of a result label.
|
||
pub fn label(english: &str) -> Text {
|
||
// The lookup is a linear scan over a static table. The table is small
|
||
// and a result carries at most a few dozen labels, so a scan is simpler
|
||
// than a map that would need to be built at startup.
|
||
for (key, zh) in PAIRS {
|
||
if *key == english {
|
||
// Both members are borrowed from the static table, so the
|
||
// result is `'static`.
|
||
return Text::new(zh, key);
|
||
}
|
||
}
|
||
// A label that is not in the table is a symbol. It is shown as it is in
|
||
// both languages, which is what a reader of either language expects for
|
||
// a name.
|
||
Text::new(own(english), own(english))
|
||
}
|
||
|
||
/// Leaks a runtime string so that it can be carried by a [`Text`].
|
||
///
|
||
/// The number of distinct labels is bounded by the tool table and every
|
||
/// label is a literal written in a handler, so the leaks are bounded and
|
||
/// happen once per distinct label. This keeps [`Text`] a `Copy` type with
|
||
/// static fields, which is what the registry and the result shape rely on.
|
||
fn own(text: &str) -> &'static str {
|
||
use std::collections::HashSet;
|
||
use std::sync::Mutex;
|
||
use std::sync::OnceLock;
|
||
|
||
static SEEN: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
|
||
let seen = SEEN.get_or_init(|| Mutex::new(HashSet::new()));
|
||
let mut guard = match seen.lock() {
|
||
Ok(guard) => guard,
|
||
Err(poisoned) => poisoned.into_inner(),
|
||
};
|
||
if let Some(existing) = guard.get(text) {
|
||
return existing;
|
||
}
|
||
let leaked: &'static str = Box::leak(text.to_string().into_boxed_str());
|
||
guard.insert(leaked);
|
||
leaked
|
||
}
|
||
|
||
/// The label pairs, ordered as the labels appear in the results.
|
||
const PAIRS: &[(&str, &str)] = &[
|
||
// Element set parsing
|
||
("Name", "名称"),
|
||
("Catalog number", "编目号"),
|
||
("Classification", "密级"),
|
||
("International designator", "国际编号"),
|
||
("Element set number", "元素集编号"),
|
||
("Revolution number", "圈数"),
|
||
("Epoch", "历元"),
|
||
("Field", "字段"),
|
||
("Value", "数值"),
|
||
("Meaning", "含义"),
|
||
("Mean motion", "平均运动"),
|
||
(
|
||
"First derivative of mean motion, divided by two",
|
||
"平均运动一阶导数的二分之一",
|
||
),
|
||
(
|
||
"Second derivative of mean motion, divided by six",
|
||
"平均运动二阶导数的六分之一",
|
||
),
|
||
("SGP4 drag term", "SGP4 阻力项"),
|
||
("Inclination", "倾角"),
|
||
("Right ascension of the ascending node", "升交点赤经"),
|
||
("Eccentricity", "偏心率"),
|
||
("Argument of perigee", "近地点幅角"),
|
||
("Mean anomaly", "平近点角"),
|
||
("Orbital period", "轨道周期"),
|
||
(
|
||
"Orbital period implied by the mean motion",
|
||
"由平均运动推出的轨道周期",
|
||
),
|
||
("Semi-major axis estimate", "半长轴估计"),
|
||
("Unit", "单位"),
|
||
("first", "第一次"),
|
||
("Two-line element set", "两行元素集"),
|
||
("second", "第二次"),
|
||
// Propagation
|
||
("Branch", "分支"),
|
||
("Minutes from epoch", "距历元分钟数"),
|
||
("Minutes", "分钟"),
|
||
("Position X", "位置 X"),
|
||
("Position Y", "位置 Y"),
|
||
("Position Z", "位置 Z"),
|
||
("Velocity X", "速度 X"),
|
||
("Velocity Y", "速度 Y"),
|
||
("Velocity Z", "速度 Z"),
|
||
("Radius", "半径"),
|
||
("Altitude above the equatorial radius", "赤道半径以上的高度"),
|
||
(
|
||
"Altitude above the equatorial radius at the lowest point",
|
||
"最低点处赤道半径以上的高度",
|
||
),
|
||
("Speed", "速率"),
|
||
("Semi-major axis", "半长轴"),
|
||
("Component", "分量"),
|
||
("Propagated state", "传播得到的状态"),
|
||
("Samples", "采样点数"),
|
||
("Duration", "时长"),
|
||
("Lowest radius", "最低半径"),
|
||
("Highest radius", "最高半径"),
|
||
("Sampled ground track", "采样的星下点轨迹"),
|
||
("Mean", "平均"),
|
||
("Osculating", "密切"),
|
||
("Element", "元素"),
|
||
("Semi-major axis change", "半长轴变化"),
|
||
("Eccentricity change", "偏心率变化"),
|
||
("Inclination change", "倾角变化"),
|
||
("Mean and osculating elements", "平均元素与密切元素"),
|
||
// Keplerian conversion
|
||
("Semi-major axis", "半长轴"),
|
||
("Perigee radius", "近地点半径"),
|
||
("Apogee radius", "远地点半径"),
|
||
("Period", "周期"),
|
||
("Semi-latus rectum", "半通径"),
|
||
("Semi-minor axis", "短半轴"),
|
||
("Quantity", "量"),
|
||
("Quantity", "量"),
|
||
(
|
||
"Keplerian elements to a state vector",
|
||
"开普勒元素转状态矢量",
|
||
),
|
||
// Time
|
||
("Scale", "尺度"),
|
||
("Modified Julian Date", "约化儒略日"),
|
||
("Calendar date", "日历日期"),
|
||
("Offset from TAI", "相对 TAI 的偏移"),
|
||
("TAI minus UTC at this instant", "该时刻的 TAI 减 UTC"),
|
||
("Time scale conversion", "时间尺度转换"),
|
||
// Geodesy
|
||
("X", "X"),
|
||
("Y", "Y"),
|
||
("Z", "Z"),
|
||
("Geocentric radius", "地心半径"),
|
||
("Prime vertical radius of curvature", "卯酉圈曲率半径"),
|
||
("Meridian radius of curvature", "子午圈曲率半径"),
|
||
("Geocentric latitude", "地心纬度"),
|
||
("Geodetic minus geocentric latitude", "大地纬度减地心纬度"),
|
||
(
|
||
"Geodetic position to Earth-fixed coordinates",
|
||
"大地坐标转地固坐标",
|
||
),
|
||
// Frames
|
||
("Direction", "方向"),
|
||
("Input X", "输入 X"),
|
||
("Input Y", "输入 Y"),
|
||
("Input Z", "输入 Z"),
|
||
("Output X", "输出 X"),
|
||
("Output Y", "输出 Y"),
|
||
("Output Z", "输出 Z"),
|
||
("Input radius", "输入半径"),
|
||
("Output radius", "输出半径"),
|
||
("Radius change", "半径变化"),
|
||
("Earth Rotation Angle", "地球自转角"),
|
||
("Input", "输入"),
|
||
("Output", "输出"),
|
||
("Frame rotation", "参考系旋转"),
|
||
// Maneuver
|
||
("First burn", "第一次点火"),
|
||
("Second burn", "第二次点火"),
|
||
("Total velocity change", "总速度增量"),
|
||
("Transfer semi-major axis", "转移轨道半长轴"),
|
||
("Transfer time", "转移时间"),
|
||
("Burn", "点火"),
|
||
("Signed", "带符号"),
|
||
("Magnitude", "大小"),
|
||
("Hohmann transfer", "霍曼转移"),
|
||
// Notes shown with a result
|
||
(
|
||
"Angles are reported in the units the element set uses, which are degrees for the orientation angles.",
|
||
"角度按元素集所用的单位报告,姿态角为度。",
|
||
),
|
||
(
|
||
"The semi-major axis estimate follows from the mean motion and Kepler's third law with the WGS 72 gravitational parameter; it ignores the perturbations the propagator models.",
|
||
"半长轴估计由平均运动与开普勒第三定律、配合 WGS 72 引力参数得出;它忽略了传播器所建模的摄动。",
|
||
),
|
||
(
|
||
"The frame is the True Equator Mean Equinox frame, which is the frame the model defines.",
|
||
"坐标系为真赤道平春分点坐标系,即模型所定义的坐标系。",
|
||
),
|
||
(
|
||
"Each sample is one propagation of the mean elements; the table is not interpolated.",
|
||
"每个采样点是对平均元素的一次传播;表中数据未经插值。",
|
||
),
|
||
(
|
||
"The track is given in the True Equator Mean Equinox frame, so a ground projection needs the Earth rotation in addition.",
|
||
"轨迹位于真赤道平春分点坐标系,因此地面投影还需额外施加地球自转。",
|
||
),
|
||
(
|
||
"The mean elements are the ones written in the element set and are the inputs of the propagator.",
|
||
"平均元素即元素集所写的元素,是传播器的输入。",
|
||
),
|
||
(
|
||
"The osculating elements describe the two-body orbit that matches the instantaneous state; they are an output of the propagation, not an input.",
|
||
"密切元素描述与瞬时状态相符的二体轨道;它是传播的输出,不是输入。",
|
||
),
|
||
(
|
||
"The mean semi-major axis follows from the mean motion with Kepler's third law, which is an approximation the propagator refines.",
|
||
"平均半长轴由平均运动与开普勒第三定律得出,该近似会被传播器进一步修正。",
|
||
),
|
||
(
|
||
"The row labelled true_anomaly compares the mean anomaly of the element set with the true anomaly of the osculating elements; the two are different angles and the comparison shows the difference between them.",
|
||
"标为 true_anomaly 的行比较元素集的平近点角与密切元素的真近点角;两者是不同的角,该行显示其差异。",
|
||
),
|
||
(
|
||
"The conversion is the two-body solution; it does not include the perturbations that SGP4 models.",
|
||
"该转换为二体解;不包含 SGP4 所建模的摄动。",
|
||
),
|
||
(
|
||
"The instant is stored as a Modified Julian Date on the TAI scale, so the difference between two instants is exact elapsed time.",
|
||
"时刻以 TAI 尺度上的约化儒略日存储,因此两个时刻之差恰为经过时间。",
|
||
),
|
||
(
|
||
"UTC is not a uniform scale; the offset column is the difference between TAI and the scale at that instant.",
|
||
"UTC 不是均匀尺度;偏移列为该时刻 TAI 与该尺度之差。",
|
||
),
|
||
(
|
||
"This instant precedes the start of the leap second table, so it has no UTC representation.",
|
||
"该时刻早于闰秒表的起点,因此没有 UTC 表示。",
|
||
),
|
||
(
|
||
"The ellipsoid is WGS 84; its semi-major axis and inverse flattening are the defining parameters.",
|
||
"椭球为 WGS 84;其长半轴与扁率倒数是定义参数。",
|
||
),
|
||
(
|
||
"Latitude is geodetic, measured from the ellipsoid normal, not geocentric; the difference is largest at mid latitudes.",
|
||
"纬度为大地纬度,自椭球法线量起,不是地心纬度;两者在中纬度差异最大。",
|
||
),
|
||
(
|
||
"The rotation is about the third axis by the Earth Rotation Angle and preserves the length of the vector.",
|
||
"该旋转为绕第三轴转动地球自转角,保持矢量长度不变。",
|
||
),
|
||
(
|
||
"Passing an Earth-fixed position through the inertial frame and back returns the Earth-fixed position; the angle is not reduced to one turn.",
|
||
"把地固位置经惯性系再转回可得到原地固位置;该角不归约到一圈之内。",
|
||
),
|
||
(
|
||
"The burns are along the direction of motion; a positive value raises the orbit and a negative value lowers it.",
|
||
"两次点火沿运动方向;正值抬升轨道,负值降低轨道。",
|
||
),
|
||
(
|
||
"The transfer is the cheapest two impulse transfer between two coplanar circular orbits in the impulsive model.",
|
||
"在脉冲模型中,这是两个共面圆轨道之间最省燃料的两次脉冲转移。",
|
||
),
|
||
(
|
||
"The coast between the burns is half the period of the transfer ellipse.",
|
||
"两次点火之间的滑行为转移椭圆周期的一半。",
|
||
),
|
||
// Chart axes and series
|
||
("Minutes from epoch", "距历元分钟数"),
|
||
];
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn a_known_label_is_translated() {
|
||
let text = label("Semi-major axis");
|
||
assert_eq!(text.en, "Semi-major axis");
|
||
assert_eq!(text.zh, "半长轴");
|
||
}
|
||
|
||
#[test]
|
||
fn an_unknown_label_is_shown_as_it_is() {
|
||
let text = label("position.x");
|
||
assert_eq!(text.en, "position.x");
|
||
assert_eq!(text.zh, "position.x");
|
||
}
|
||
|
||
#[test]
|
||
fn a_repeated_lookup_returns_the_same_static_text() {
|
||
let first = label("not-a-known-label");
|
||
let second = label("not-a-known-label");
|
||
assert_eq!(first.zh.as_ptr(), second.zh.as_ptr());
|
||
}
|
||
}
|