Files
fenix8v3-watchface/tools/gen_fields.py
ericwyuan d9d45d983b 接入系统 Complication,补齐倒计时/自定义格式/28天累计/空气质量
字段 125 → 156 项。

Toybox.Complications(source/Comp.mc)
  这条路推翻了我之前几个「表盘做不到」的判断 —— 当时只查了 ActivityMonitor /
  UserProfile / Weather,漏了系统把这些作为 complication 发布出来:
    - 下一个日程(CIQ 确实没有日历 API,但有 CALENDAR_EVENTS complication)
    - 训练状态、完赛预测 ×4、配速预测 ×4、最近高尔夫得分
    - 本周跑步 / 骑行距离(按运动分类的,按天历史做不出来)
    - 第三方 App 1–5(CGM、补水、行情这类 App 发布的)
  需要 ComplicationSubscriber 权限。

其余补齐
  - 近 28 天累计:步数 / 卡路里 / 距离 / 楼层
  - 事件倒计时:设置里填 YYYY-MM-DD,显示还剩几天
  - 自定义时间格式、日期格式 1/2:单字母占位符(h/H/m/s/a/D/M/N/Y/y/w)
  - OWM 空气质量:AQI 等级 + 描述 + PM2.5 + PM10 + CO,走
    /data/2.5/air_pollution —— 那个接口在免费档就开放,不需要 One Call 订阅

运行时踩的坑
  - Complication.unit 不保证是 String(周跑步距离返回的就不是),直接调
    .length() 抛 Symbol Not Found。unit 和 shortLabel 都改成先 instanceof 判型。
  - 浮点 value 直接 toString 印成 "0.000000",统一格式化成一位小数。
  - 第三方 App 槽位每取一次就要走完整个迭代器,四个格子都选 App 时会走四遍。
    改成每帧只枚举一次,由 view 在绘制开始时调 Comp.beginFrame() 失效。

测试
  全字段验证的 harness 本身会触发看门狗(一次 onUpdate 里算 156 个字段),
  改成分批每帧 12 个。实际使用一帧只算 4 个,不受影响。
  156 项全部可渲染,零运行时错误;17 款设备全部编译通过。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 09:03:21 +08:00

369 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""一张表 → 字段 id 表、两种语言的文案、以及设置项 XML。
字段编号沿用 https://watchface.io/docs/datafields方便两边对照但这里只收录
Connect IQ 在手表本机能算出来的字段。
需要联网服务的OpenWeatherMap 的部分、StormGlass、需要手机侧与 Garmin
Connect 集成的(按运动分类的周/月累计)、需要用户装了别的 App 的CGM、
补水、行情),都不在这里 —— 具体原因见 README 第 7 节。
python3 tools/gen_fields.py
产出:
source/FieldTable.mc 字段 id 表 + 下标反查
resources/settings/settings.xml 四个数据位的选择器
resources/strings/fields.xml 英文的字段名与表盘标签
resources-chn/strings/fields.xml 中文的字段名与表盘标签
加字段的流程:在下面的 FIELDS 表里加一行 → 跑一遍本脚本 →
到 source/Fields.mc 对应分类的函数里补一个分支。设置项、两种语言的文案、
表盘上的标签都会自动跟上,不会漏。
"""
import os
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
# id, key, English name, Chinese name, on-face label (en), on-face label (cn)
FIELDS = [
(0, "Off", "Off", "关闭", "", ""),
# ---- date / time ----------------------------------------------------
(860, "Time", "Time", "时间", "TIME", "时间"),
(880, "UtcTime", "UTC Time", "UTC 时间", "UTC", "UTC"),
(864, "AltTime1", "Alternate Time 1", "第二时区 1", "TZ1", "时区1"),
(865, "AltTime2", "Alternate Time 2", "第二时区 2", "TZ2", "时区2"),
(851, "DayMonthDay", "Day Month Day", "星期 月 日", "DATE", "日期"),
(872, "DayDate", "Day Date", "星期 日", "DATE", "日期"),
(852, "MonthDay", "Month Day", "月 日", "DATE", "日期"),
(855, "DayOfMonth", "Day of Month", "", "DAY", ""),
(870, "DayShort", "Day (Tue)", "星期简写", "DAY", "星期"),
(856, "DayFull", "Full Day (Tuesday)", "星期全称", "DAY", "星期"),
(853, "MonthName", "Month (Jan)", "月份", "MON", ""),
(868, "MonthNum", "Month (6)", "月份数字", "MON", ""),
(858, "Meridiem", "AM/PM", "上午/下午", "", ""),
(859, "WeekIso", "Week Number (ISO)", "周数 (ISO)", "WEEK", ""),
(850, "Seconds", "Seconds", "", "SEC", ""),
(869, "Seconds0", "Seconds (09)", "秒 (前导零)", "SEC", ""),
(873, "WeekCal", "Week Number", "周数", "WEEK", ""),
(861, "CustomTime", "Custom Time Format", "自定义时间格式", "TIME", "时间"),
(862, "DateFmt1", "Date Format 1", "日期格式 1", "DATE", "日期"),
(863, "DateFmt2", "Date Format 2", "日期格式 2", "DATE", "日期"),
(735, "Countdown", "Event Countdown", "事件倒计时", "DAYS", "倒计时"),
# ---- activity -------------------------------------------------------
(1, "Steps", "Steps", "步数", "STEP", "步数"),
(14, "StepsRound", "Steps Rounded", "步数 (取整)", "STEP", "步数"),
(707, "StepsToGo", "Steps to Go", "剩余步数", "TO GO", "还差"),
(2, "Calories", "Calories", "卡路里", "KCAL", "千卡"),
(603, "ActiveCal", "Active Calories", "活动卡路里", "ACAL", "活动"),
(150, "ActiveMin", "Active Minutes", "活动分钟", "MIN", "分钟"),
(152, "ActiveMod", "Active Min (Moderate)", "中强度分钟", "MOD", "中强度"),
(154, "ActiveVig", "Active Min (Vigorous)", "高强度分钟", "VIG", "高强度"),
(151, "ActiveWeek", "Active Minutes (Week)", "本周活动分钟", "WEEK", "周分钟"),
(3, "Distance", "Distance", "距离", "DST", "距离"),
(4, "FloorsUp", "Floors Climbed", "上楼层数", "FLR", "楼层"),
(5, "FloorsDown", "Floors Descended", "下楼层数", "DESC", "下楼"),
(6, "MetersUp", "Distance Climbed", "爬升高度", "ASC", "爬升"),
(9, "PushDist", "Push Distance", "推行距离", "PUSH", "推行"),
(10, "Pushes", "Pushes", "推行次数", "PUSH", "推行"),
(219, "StepsWeek", "Steps (Week to Date)", "本周步数", "WEEK", "周步数"),
(220, "Steps7d", "Steps (Last 7 Days)", "近 7 天步数", "7D", "7天"),
(211, "CalWeek", "Calories (Week to Date)", "本周卡路里", "WEEK", "周千卡"),
(212, "Cal7d", "Calories (Last 7 Days)", "近 7 天卡路里", "7D", "7天"),
(213, "DistWeek", "Distance (Week to Date)", "本周距离", "WEEK", "周距离"),
(214, "Dist7d", "Distance (Last 7 Days)", "近 7 天距离", "7D", "7天"),
(203, "FloorsWeek", "Floors (Week to Date)", "本周楼层", "WEEK", "周楼层"),
(204, "Floors7d", "Floors (Last 7 Days)", "近 7 天楼层", "7D", "7天"),
(200, "ActiveMin7d", "Active Minutes (7 Days)", "近 7 天活动分钟", "7D", "7天"),
(828, "Steps28d", "Steps (28 Days)", "近 28 天步数", "28D", "28天"),
(832, "Cal28d", "Calories (28 Days)", "近 28 天卡路里", "28D", "28天"),
(836, "Dist28d", "Distance (28 Days)", "近 28 天距离", "28D", "28天"),
(840, "Floors28d", "Floors (28 Days)", "近 28 天楼层", "28D", "28天"),
# ---- body -----------------------------------------------------------
(618, "HeartRate", "Heart Rate", "心率", "HR", "心率"),
(276, "RestingHr", "Resting Heart Rate", "静息心率", "RHR", "静息"),
(12, "Stress", "Stress Score", "压力指数", "STRS", "压力"),
(635, "PulseOx", "Pulse Ox", "血氧", "SPO2", "血氧"),
(11, "Respiration", "Respiration Rate", "呼吸频率", "RESP", "呼吸"),
(623, "BodyBattery", "Body Battery", "身体电量", "BODY", "体能"),
(8, "MoveBar", "Move Bar", "活动条", "MOVE", "活动"),
(15, "MoveBarRev", "Move Bar Reversed", "活动条 (反向)", "MOVE", "活动"),
(281, "Weight", "Weight", "体重", "WT", "体重"),
(742, "Bmi", "BMI", "BMI", "BMI", "BMI"),
(13, "Recovery", "Time to Recovery", "恢复时间", "RCVR", "恢复"),
(278, "Vo2Run", "VO2 Max (Run)", "最大摄氧量 (跑)", "VO2", "摄氧"),
(277, "Vo2Bike", "VO2 Max (Bike)", "最大摄氧量 (骑)", "VO2", "摄氧"),
# ---- system ---------------------------------------------------------
(250, "Battery", "Battery", "电量", "BATT", "电量"),
(251, "BatteryDays", "Battery in Days", "剩余天数", "DAYS", "天数"),
(257, "BatteryBoth", "Battery / Days", "电量 / 天数", "BATT", "电量"),
(253, "Alarms", "Alarms", "闹钟", "ALRM", "闹钟"),
(255, "Notifications", "Notifications", "通知", "MSG", "通知"),
(254, "DoNotDisturb", "Do Not Disturb", "勿扰模式", "DND", "勿扰"),
(256, "Bluetooth", "Bluetooth", "蓝牙", "BT", "蓝牙"),
(252, "Solar", "Solar Intensity", "太阳能强度", "SOLR", "太阳能"),
# ---- environment ----------------------------------------------------
(615, "Altitude", "Altitude", "海拔", "ALT", "海拔"),
(616, "SeaPressure", "Sea Level Pressure", "海平面气压", "hPa", "气压"),
(733, "AmbPressure", "Ambient Pressure", "环境气压", "hPa", "气压"),
(712, "Sunrise", "Sunrise", "日出", "RISE", "日出"),
(717, "Sunset", "Sunset", "日落", "SET", "日落"),
(700, "NextSun", "Next Sun Event", "下一太阳事件", "SUN", "太阳"),
(731, "UntilSun", "Time Until Sun Event", "距太阳事件", "SUN", "太阳"),
(706, "MoonAge", "Moon Age", "月龄", "MOON", "月龄"),
(732, "MoonLit", "Moon Illumination", "月球照明", "MOON", "月相"),
(710, "CivilAm", "Civil Twilight Start", "民用晨光始", "CIVIL", "民用晨"),
(709, "NauticalAm", "Nautical Twilight Start", "航海晨光始", "NAUT", "航海晨"),
(708, "AstroAm", "Astronomical Twilight Start", "天文晨光始", "ASTR", "天文晨"),
(711, "BlueAm", "Blue Hour (AM)", "蓝调时刻 (晨)", "BLUE", "蓝调晨"),
(714, "GoldenAm", "Golden Hour (AM)", "黄金时刻 (晨)", "GOLD", "黄金晨"),
(715, "GoldenPm", "Golden Hour (PM)", "黄金时刻 (昏)", "GOLD", "黄金昏"),
(718, "BluePm", "Blue Hour (PM)", "蓝调时刻 (昏)", "BLUE", "蓝调昏"),
(719, "CivilPm", "Civil Twilight End", "民用昏影终", "CIVIL", "民用昏"),
(720, "NauticalPm", "Nautical Twilight End", "航海昏影终", "NAUT", "航海昏"),
(721, "AstroPm", "Astronomical Twilight End", "天文昏影终", "ASTR", "天文昏"),
# ---- weather --------------------------------------------------------
(306, "Temp", "Temperature", "气温", "TEMP", "气温"),
(639, "TempHiLo", "Temperature High/Low", "最高/最低气温", "HI/LO", "高低温"),
(300, "FeelsLike", "Feels Like", "体感温度", "FEEL", "体感"),
(301, "TempHigh", "High Temperature", "最高气温", "HIGH", "最高"),
(302, "TempLow", "Low Temperature", "最低气温", "LOW", "最低"),
(305, "Humidity", "Humidity", "湿度", "HUM", "湿度"),
(304, "Precip", "Precipitation %", "降水概率", "RAIN", "降水"),
(308, "WindSpeed", "Wind Speed", "风速", "WIND", "风速"),
(307, "WindBearing", "Wind Bearing", "风向角", "WIND", "风向"),
(310, "WindDir", "Wind Direction", "风向", "WIND", "风向"),
(309, "WeatherTime", "Weather Update Time", "天气更新时间", "WX", "更新"),
(312, "PressureSea", "Pressure (Sea Level)", "海平面气压", "hPa", "气压"),
(313, "Uvi", "UV Index", "紫外线指数", "UVI", "紫外线"),
(314, "Visibility", "Visibility", "能见度", "VIS", "能见度"),
(315, "DewPoint", "Dew Point", "露点", "DEW", "露点"),
# ---- 系统 Complication含第三方 App 发布的)------------------------
# 这些走 Toybox.Complications见 source/Comp.mc。
(612, "NextCal", "Next Calendar Event", "下一个日程", "CAL", "日程"),
(626, "TrainStatus", "Training Status", "训练状态", "TRN", "训练"),
(627, "Race5k", "Race Time (5k)", "5k 完赛预测", "5K", "5K"),
(628, "Race10k", "Race Time (10k)", "10k 完赛预测", "10K", "10K"),
(629, "RaceHalf", "Race Time (Half)", "半马完赛预测", "HM", "半马"),
(630, "RaceFull", "Race Time (Marathon)", "全马完赛预测", "FM", "全马"),
(631, "Pace5k", "Pace (5k)", "5k 配速预测", "5K", "5K配速"),
(632, "Pace10k", "Pace (10k)", "10k 配速预测", "10K", "10K配速"),
(633, "PaceHalf", "Pace (Half)", "半马配速预测", "HM", "半马配速"),
(634, "PaceFull", "Pace (Marathon)", "全马配速预测", "FM", "全马配速"),
(641, "GolfScore", "Last Golf Round Score", "最近高尔夫得分", "GOLF", "高尔夫"),
(619, "RunWeek", "Run Distance (Week)", "本周跑步距离", "RUN", "跑步"),
(620, "BikeWeek", "Bike Distance (Week)", "本周骑行距离", "BIKE", "骑行"),
(695, "App1", "App 1 (on watch)", "第三方 App 1", "APP1", "App1"),
(696, "App2", "App 2 (on watch)", "第三方 App 2", "APP2", "App2"),
(697, "App3", "App 3 (on watch)", "第三方 App 3", "APP3", "App3"),
(698, "App4", "App 4 (on watch)", "第三方 App 4", "APP4", "App4"),
(699, "App5", "App 5 (on watch)", "第三方 App 5", "APP5", "App5"),
# ---- OpenWeatherMap -------------------------------------------------
# Fetched by the background service; see source/Owm.mc.
(500, "OwmTemp", "OWM Temperature", "OWM 气温", "TEMP", "气温"),
(515, "OwmMinMax", "OWM Max/Min Temp", "OWM 最高/最低", "HI/LO", "高低温"),
(503, "OwmFeels", "OWM Feels Like", "OWM 体感温度", "FEEL", "体感"),
(504, "OwmMax", "OWM Temp Max", "OWM 最高温", "HIGH", "最高"),
(505, "OwmMin", "OWM Temp Min", "OWM 最低温", "LOW", "最低"),
(507, "OwmDescShort", "OWM Description", "OWM 天气", "WX", "天气"),
(502, "OwmDescLong", "OWM Description (Long)", "OWM 天气详述", "WX", "天气"),
(501, "OwmHumidity", "OWM Humidity", "OWM 湿度", "HUM", "湿度"),
(506, "OwmPressure", "OWM Pressure", "OWM 气压", "hPa", "气压"),
(509, "OwmVisibility", "OWM Visibility", "OWM 能见度", "VIS", "能见度"),
(510, "OwmWind", "OWM Wind Speed", "OWM 风速", "WIND", "风速"),
(511, "OwmWindDeg", "OWM Wind Degrees", "OWM 风向角", "WIND", "风向"),
(518, "OwmWindDir", "OWM Wind Direction", "OWM 风向", "WIND", "风向"),
(512, "OwmGust", "OWM Wind Gust", "OWM 阵风", "GUST", "阵风"),
(513, "OwmRain", "OWM Rain 1h", "OWM 1h 降雨", "RAIN", "降雨"),
(532, "OwmSnow", "OWM Snow 1h", "OWM 1h 降雪", "SNOW", "降雪"),
(536, "OwmClouds", "OWM Cloud Cover", "OWM 云量", "CLD", "云量"),
(537, "OwmPrecip", "OWM Precipitation %", "OWM 降水概率", "RAIN", "降水"),
(530, "OwmDew", "OWM Dew Point", "OWM 露点", "DEW", "露点"),
(534, "OwmTomorrow", "OWM Temp Tomorrow", "OWM 明日气温", "TMRW", "明日"),
(522, "OwmMinMaxTmrw", "OWM Min/Max Tomorrow", "OWM 明日高低温", "TMRW", "明日"),
(535, "OwmDayAfter", "OWM Temp Day After", "OWM 后日气温", "D+2", "后日"),
(523, "OwmMinMaxD2", "OWM Min/Max Day After", "OWM 后日高低温", "D+2", "后日"),
(538, "OwmAqi", "OWM Air Quality (AQI)", "OWM 空气质量", "AQI", "空气"),
(539, "OwmAqiText", "OWM AQI Description", "OWM 空气质量描述", "AQI", "空气"),
(540, "OwmPm25", "OWM PM2.5", "OWM PM2.5", "PM2.5", "PM2.5"),
(541, "OwmPm10", "OWM PM10", "OWM PM10", "PM10", "PM10"),
(542, "OwmCo", "OWM CO", "OWM 一氧化碳", "CO", "CO"),
(516, "OwmCity", "OWM City Name", "OWM 城市", "CITY", "城市"),
(517, "OwmUpdated", "OWM Update Time", "OWM 更新时间", "OWM", "更新"),
# ---- custom ---------------------------------------------------------
(703, "Custom1", "Custom Text 1", "自定义文字 1", "", ""),
(704, "Custom2", "Custom Text 2", "自定义文字 2", "", ""),
(705, "Custom3", "Custom Text 3", "自定义文字 3", "", ""),
]
SLOTS = [("LeftTop", "Top-Left Field", "左上数据"),
("RightTop", "Top-Right Field", "右上数据"),
("BottomLeft", "Bottom-Left Field", "左下数据"),
("BottomRight", "Bottom-Right Field", "右下数据")]
RING = [(0, "Steps", "Steps", "步数"), (1, "Calories", "Calories", "卡路里"),
(2, "Distance", "Distance", "距离"), (3, "Floors", "Floors", "楼层"),
(4, "MoveBar", "Move Bar", "动动条"), (5, "ActiveMin", "Active Minutes", "活动分钟"),
(6, "BodyBattery", "Body Battery", "身体电量")]
OTHER_SETTINGS = """
<setting propertyKey="@Properties.TempUnit" title="@Strings.SettingTempUnit">
<settingConfig type="list">
<listEntry value="0">@Strings.TempUnitAuto</listEntry>
<listEntry value="1">@Strings.TempUnitC</listEntry>
<listEntry value="2">@Strings.TempUnitF</listEntry>
</settingConfig>
</setting>
<setting propertyKey="@Properties.AltOffset1" title="@Strings.SettingAltOffset1">
<settingConfig type="numeric" min="-720" max="840"/>
</setting>
<setting propertyKey="@Properties.AltOffset2" title="@Strings.SettingAltOffset2">
<settingConfig type="numeric" min="-720" max="840"/>
</setting>
<setting propertyKey="@Properties.CountdownDate" title="@Strings.SettingCountdownDate">
<settingConfig type="alphaNumeric" maxLength="10"/>
</setting>
<setting propertyKey="@Properties.TimeFmt" title="@Strings.SettingTimeFmt">
<settingConfig type="alphaNumeric" maxLength="16"/>
</setting>
<setting propertyKey="@Properties.DateFmt1" title="@Strings.SettingDateFmt1">
<settingConfig type="alphaNumeric" maxLength="16"/>
</setting>
<setting propertyKey="@Properties.DateFmt2" title="@Strings.SettingDateFmt2">
<settingConfig type="alphaNumeric" maxLength="16"/>
</setting>
<setting propertyKey="@Properties.OwmKey" title="@Strings.SettingOwmKey">
<settingConfig type="alphaNumeric" maxLength="40"/>
</setting>
<setting propertyKey="@Properties.Custom1" title="@Strings.SettingCustom1">
<settingConfig type="alphaNumeric" maxLength="12"/>
</setting>
<setting propertyKey="@Properties.Custom2" title="@Strings.SettingCustom2">
<settingConfig type="alphaNumeric" maxLength="12"/>
</setting>
<setting propertyKey="@Properties.Custom3" title="@Strings.SettingCustom3">
<settingConfig type="alphaNumeric" maxLength="12"/>
</setting>
<setting propertyKey="@Properties.ShowTop" title="@Strings.SettingShowTop">
<settingConfig type="boolean"/>
</setting>
<setting propertyKey="@Properties.ShowBand" title="@Strings.SettingShowBand">
<settingConfig type="boolean"/>
</setting>
<setting propertyKey="@Properties.ShowBottom" title="@Strings.SettingShowBottom">
<settingConfig type="boolean"/>
</setting>
"""
def field_list(indent=8):
pad = " " * indent
return "\n".join('%s<listEntry value="%d">@Strings.Fld%s</listEntry>'
% (pad, fid, key) for fid, key, _, _, _, _ in FIELDS)
def write(path, text):
os.makedirs(os.path.dirname(path), exist_ok=True)
open(path, "w").write(text)
print("wrote", os.path.relpath(path, ROOT))
def main():
ids = [f[0] for f in FIELDS]
assert len(ids) == len(set(ids)), "duplicate field id"
# ---- FieldTable.mc ----
L = ["// ⚠️ 本文件由 tools/gen_fields.py 自动生成,请勿手改。",
"// 要加字段:在生成器的 FIELDS 表里加一行,跑一遍生成器,",
"// 再到 source/Fields.mc 对应分类的函数里补一个分支。",
"//",
"// 字段编号沿用 https://watchface.io/docs/datafields只收录 Connect IQ",
"// 表盘在本机能算出来的那些。",
"import Toybox.Lang;", "", "module FieldTable {", "",
" // 全部字段 id顺序与 FieldLabels 资源串(逗号分隔的标签表)严格一致 ——",
" // Fields.label() 就是靠这个下标去取对应标签的。",
" const IDS = ["]
row = " "
for i, fid in enumerate(ids):
piece = "%d%s" % (fid, "," if i < len(ids) - 1 else "")
if len(row) + len(piece) > 92:
L.append(row)
row = " "
row += piece
L += [row, " ];", "",
" // 由字段 id 反查它在 IDS 里的下标;找不到返回 0对应「关闭」",
" function indexOf(id as Number) as Number {",
" for (var i = 0; i < IDS.size(); i++) {",
" if (IDS[i] == id) { return i; }",
" }",
" return 0;",
" }",
"}"]
write(os.path.join(ROOT, "source", "FieldTable.mc"), "\n".join(L) + "\n")
# ---- settings.xml ----
S = ['<settings>', '',
' <setting propertyKey="@Properties.Theme" title="@Strings.SettingTheme">',
' <settingConfig type="list">']
names = ["Ember", "Aurora", "Brass", "Voltage", "Reef", "Acid", "Kelp"]
for i, n in enumerate(names):
S.append(' <listEntry value="%d">@Strings.Theme%s</listEntry>' % (i + 1, n))
S += [' </settingConfig>', ' </setting>', '']
for slot, _, _ in SLOTS:
S += [' <setting propertyKey="@Properties.%s" title="@Strings.Setting%s">' % (slot, slot),
' <settingConfig type="list">', field_list(12),
' </settingConfig>', ' </setting>', '']
for ring in ("RingTR", "RingTL", "RingBR", "RingBL"):
S += [' <setting propertyKey="@Properties.%s" title="@Strings.Setting%s">' % (ring, ring),
' <settingConfig type="list">']
for rid, key, _, _ in RING:
S.append(' <listEntry value="%d">@Strings.Metric%s</listEntry>' % (rid, key))
S += [' </settingConfig>', ' </setting>', '']
S.append(OTHER_SETTINGS.rstrip())
S.append('</settings>')
write(os.path.join(ROOT, "resources", "settings", "settings.xml"), "\n".join(S) + "\n")
# ---- field strings, both languages ----
for lang, folder, ni, li in (("en", "resources", 2, 4), ("chn", "resources-chn", 3, 5)):
X = ['<strings>']
X.append(' <!-- Field names shown in the settings picker. -->')
for f in FIELDS:
X.append(' <string id="Fld%s">%s</string>' % (f[1], f[ni]))
X.append('')
X.append(' <!-- On-face captions, in FieldTable.IDS order. -->')
X.append(' <string id="FieldLabels">%s</string>'
% ",".join(f[li] for f in FIELDS))
X.append('')
for rid, key, en, cn in RING:
X.append(' <string id="Metric%s">%s</string>' % (key, en if lang == "en" else cn))
X.append('</strings>')
write(os.path.join(ROOT, folder, "strings", "fields.xml"), "\n".join(X) + "\n")
print("%d fields, %d slots" % (len(FIELDS), len(SLOTS)))
if __name__ == "__main__":
main()