//! 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>> = 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()); } }