@cardenelabs/cdl 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import { useEffect, useLayoutEffect, useMemo, useState, useSyncExternalStore, useRef } from "react";
1
+ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useState, useSyncExternalStore, useRef } from "react";
2
2
  import type { JSX } from "react";
3
+ import { CDL_TEXT, resolveLang, 字を差し込む, type CdlLang, type CdlTextKey } from "../lang";
3
4
  import { assertNever } from "../assert-never";
4
5
  import type { CdlDiagram, CdlInput, CdlReadout } from "../types";
5
6
  import { createInputSignals, inputDefaultValue } from "../input-signals";
@@ -92,6 +93,55 @@ export type InteractiveSignalsAccessor = {
92
93
  getBool?: (id: string) => boolean | undefined;
93
94
  };
94
95
 
96
+ /**
97
+ * 操作部の言語 (#849)。 読み出しと入力欄が書く固定の字 (`empty` / `+3 more` / `Todo` など) を引き替える。
98
+ *
99
+ * **React の文脈で届ける**。 読み出しの部品は 100 を超え、すべて `ReadoutWidget` を通して React の木の中で
100
+ * 描かれる。 引数で渡すと全部品の型と呼び出しを書き換えることになり、渡し忘れた部品が英語のまま残る。
101
+ * 形の部品 (#847) が引数で渡すのは、検査が形を関数として直接呼ぶためで、読み出しにはその呼び方が無い。
102
+ *
103
+ * 既定は英語。 文脈の外で描いた部品は変更前と同じ字になる。
104
+ */
105
+ const 操作部の言語 = createContext<CdlLang>("en");
106
+
107
+ /** 操作部の言語で辞書を引き、`{名前}` に値を差し込む */
108
+ function use字引(): (key: CdlTextKey, values?: Readonly<Record<string, string | number>>) => string {
109
+ const lang = useContext(操作部の言語);
110
+ return (key, values) => (values ? 字を差し込む(CDL_TEXT[lang][key], values) : CDL_TEXT[lang][key]);
111
+ }
112
+
113
+ /**
114
+ * 辞書の字を 1 つ描く。 本文や `<title>` の中に字だけを置く所で使う。
115
+ *
116
+ * 部品ごとに hook を呼ぶ行を足さずに済む (`empty` だけで 44 の部品にある)。 描く markup は字をそのまま
117
+ * 置いた時と同じ。 属性に渡す字と、字を判定に使う所は `use字引` で引く。
118
+ */
119
+ function 字({ k, v }: { k: CdlTextKey; v?: Readonly<Record<string, string | number>> }): JSX.Element {
120
+ const 引く = use字引();
121
+ return <>{引く(k, v)}</>;
122
+ }
123
+
124
+ /**
125
+ * 辞書の字を SVG の見出し (`<title>`) として描く。
126
+ *
127
+ * **`<title>` の中に `<字>` を置かない**。 React は `<title>` の子を 1 つの文字列として扱い、部品を置くと
128
+ * `[object Object]` と書く (英語の描画の控えとの突き合わせで実測)。 見出しごとこの部品で描き、字は文字列で渡す。
129
+ *
130
+ * `前` は字の前に付ける値 (星の番号 `1: ` など)。 `k` を渡さない時は空の見出しを描く (点いていない灯)。
131
+ */
132
+ function 字の見出し({
133
+ k,
134
+ v,
135
+ 前 = "",
136
+ }: {
137
+ k?: CdlTextKey;
138
+ v?: Readonly<Record<string, string | number>>;
139
+ 前?: string;
140
+ }): JSX.Element {
141
+ const 引く = use字引();
142
+ return <title>{k === undefined ? "" : `${前}${引く(k, v)}`}</title>;
143
+ }
144
+
95
145
  /**
96
146
  * accessor を diagram + signals から構築する SSOT (CAR #255)。
97
147
  *
@@ -593,6 +643,8 @@ export function useInteractiveIntegration(
593
643
  )
594
644
  return null;
595
645
  return (
646
+ // 操作部の中の読み出しと入力欄へ、図の言語を届ける (#849)。 型の外の値は英語に落ちる
647
+ <操作部の言語.Provider value={resolveLang(diagram.lang)}>
596
648
  <div className="cdl-ip-root" data-cdl-interactive-panel="true">
597
649
  {inputs.length > 0 && (
598
650
  <div className="cdl-ip-group">
@@ -628,6 +680,7 @@ export function useInteractiveIntegration(
628
680
  </div>
629
681
  )}
630
682
  </div>
683
+ </操作部の言語.Provider>
631
684
  );
632
685
  }, [diagram, signals, stateOverrides, mergedStateValues]);
633
686
 
@@ -2493,7 +2546,7 @@ function SparklineReadout({
2493
2546
  <div className="cdl-ip-item cdl-ip-readout-sparkline" data-cdl-readout={id}>
2494
2547
  <span className="cdl-ip-label">{label}</span>
2495
2548
  <div className="cdl-ip-readout-sparkline-empty" style={{ width: w, height: h }}>
2496
- {points.length === 0 ? "waiting" : "—"}
2549
+ {points.length === 0 ? <字 k="readout.sparkline.waiting" /> : "—"}
2497
2550
  </div>
2498
2551
  <span className="cdl-ip-readout-sparkline-value">
2499
2552
  {Number.isFinite(value) ? value : "—"}
@@ -2822,12 +2875,14 @@ function TimelineWidget({
2822
2875
  onClick={cycleSpeed}
2823
2876
  aria-label="speed"
2824
2877
  >
2825
- {speed}x
2878
+ <字 k="input.timeline.speed" v={{ speed }} />
2826
2879
  </button>
2827
2880
  </div>
2828
2881
  <span className="cdl-ip-timeline-time">
2829
- {(progress * (input.duration / 1000)).toFixed(1)}s /{" "}
2830
- {(input.duration / 1000).toFixed(1)}s
2882
+ <字
2883
+ k="input.timeline.time"
2884
+ v={{ t: (progress * (input.duration / 1000)).toFixed(1), d: (input.duration / 1000).toFixed(1) }}
2885
+ />
2831
2886
  </span>
2832
2887
  </div>
2833
2888
  );
@@ -3129,7 +3184,7 @@ function ArrayBarReadout({
3129
3184
  <span className="cdl-ip-label">{label}</span>
3130
3185
  <div className="cdl-ip-readout-array-bar-row">
3131
3186
  {arr.length === 0 ? (
3132
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3187
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3133
3188
  ) : (
3134
3189
  arr.map((v, i) => {
3135
3190
  const h = Math.max(0, Math.min(1, (v - min) / range));
@@ -3242,7 +3297,7 @@ function StackedBarReadout({
3242
3297
  <span className="cdl-ip-label">{label}</span>
3243
3298
  <div className="cdl-ip-readout-stacked-bar-row">
3244
3299
  {n === 0 ? (
3245
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3300
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3246
3301
  ) : (
3247
3302
  Array.from({ length: n }).map((_, i) => {
3248
3303
  const va = arrA[i] ?? 0;
@@ -3378,7 +3433,7 @@ function MatrixReadout({
3378
3433
  <div className="cdl-ip-item cdl-ip-readout-matrix" data-cdl-readout={id}>
3379
3434
  <span className="cdl-ip-label">{label}</span>
3380
3435
  {rows === 0 || cols === 0 ? (
3381
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3436
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3382
3437
  ) : (
3383
3438
  <svg
3384
3439
  className="cdl-ip-readout-matrix-svg"
@@ -3449,7 +3504,7 @@ function ProgressGroupReadout({
3449
3504
  <div className="cdl-ip-item cdl-ip-readout-progress-group" data-cdl-readout={id}>
3450
3505
  <span className="cdl-ip-label">{label}</span>
3451
3506
  {arr.length === 0 ? (
3452
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3507
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3453
3508
  ) : (
3454
3509
  <ul className="cdl-ip-readout-progress-group-list">
3455
3510
  {arr.map((v, i) => {
@@ -3539,7 +3594,7 @@ function SequenceTimelineReadout({
3539
3594
  <div className="cdl-ip-item cdl-ip-readout-sequence-timeline" data-cdl-readout={id}>
3540
3595
  <span className="cdl-ip-label">{label}</span>
3541
3596
  {events.length === 0 ? (
3542
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3597
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3543
3598
  ) : (
3544
3599
  <svg
3545
3600
  className="cdl-ip-readout-sequence-timeline-svg"
@@ -3558,7 +3613,7 @@ function SequenceTimelineReadout({
3558
3613
  return (
3559
3614
  <g key={i} data-cdl-event-marker={e.name}>
3560
3615
  <circle cx={x} cy={trackY} r={5} fill={color} stroke="#fff" strokeWidth={1.5}>
3561
- <title>{`${e.name}: t=${e.t}`}</title>
3616
+ <字の見出し k="readout.sequenceTimeline.title" v={{ name: e.name, t: e.t }} />
3562
3617
  </circle>
3563
3618
  <text x={x} y={trackY - 12} textAnchor={sequenceLabelAnchor(x, e.name, 10, viewW)} fontSize={10} fill="var(--v4-ink, #1a1f2a)">
3564
3619
  {e.name}
@@ -3624,7 +3679,7 @@ function RadarReadout({
3624
3679
  <div className="cdl-ip-item cdl-ip-readout-radar" data-cdl-readout={id}>
3625
3680
  <span className="cdl-ip-label">{label}</span>
3626
3681
  {n === 0 ? (
3627
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3682
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3628
3683
  ) : (
3629
3684
  <svg className="cdl-ip-readout-radar-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
3630
3685
  {[0.33, 0.66, 1].map((t) => (
@@ -3716,7 +3771,7 @@ function BubbleChartReadout({
3716
3771
  <div className="cdl-ip-item cdl-ip-readout-bubble-chart" data-cdl-readout={id}>
3717
3772
  <span className="cdl-ip-label">{label}</span>
3718
3773
  {bubbles.length === 0 ? (
3719
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3774
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3720
3775
  ) : (
3721
3776
  <svg className="cdl-ip-readout-bubble-chart-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
3722
3777
  <line x1={pad} x2={viewW - pad} y1={viewH - pad} y2={viewH - pad} stroke="rgba(148,163,184,0.4)" />
@@ -3727,7 +3782,7 @@ function BubbleChartReadout({
3727
3782
  const r = 4 + ((Math.max(rMin, Math.min(rMax, b.r)) - rMin) / rRange) * (maxCircleR - 4);
3728
3783
  return (
3729
3784
  <circle key={i} cx={cx} cy={cy} r={r} fill={color} fillOpacity={0.35} stroke={color} strokeWidth={1.5}>
3730
- <title>{`(${b.x}, ${b.y}) r=${b.r}`}</title>
3785
+ <字の見出し k="readout.bubbleChart.title" v={{ x: b.x, y: b.y, r: b.r }} />
3731
3786
  </circle>
3732
3787
  );
3733
3788
  })}
@@ -3765,7 +3820,7 @@ function DonutReadout({
3765
3820
  <div className="cdl-ip-item cdl-ip-readout-donut" data-cdl-readout={id}>
3766
3821
  <span className="cdl-ip-label">{label}</span>
3767
3822
  {arr.length === 0 || total === 0 ? (
3768
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
3823
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
3769
3824
  ) : (
3770
3825
  <svg className="cdl-ip-readout-donut-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
3771
3826
  {arr.map((v, i) => {
@@ -3849,7 +3904,7 @@ function CalendarHeatmapReadout({
3849
3904
  rx={2}
3850
3905
  fill={fill}
3851
3906
  >
3852
- <title>{`day ${i}: ${v}`}</title>
3907
+ <字の見出し k="readout.calendarHeatmap.title" v={{ i, v }} />
3853
3908
  </rect>
3854
3909
  );
3855
3910
  })}
@@ -4013,7 +4068,7 @@ function CandlestickReadout({
4013
4068
  <div className="cdl-ip-item cdl-ip-readout-candlestick" data-cdl-readout={id}>
4014
4069
  <span className="cdl-ip-label">{label}</span>
4015
4070
  {candles.length === 0 ? (
4016
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4071
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4017
4072
  ) : (
4018
4073
  <svg className="cdl-ip-readout-candlestick-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4019
4074
  {candles.map((cd, i) => {
@@ -4037,7 +4092,7 @@ function CandlestickReadout({
4037
4092
  fill={isUp ? color : color}
4038
4093
  fillOpacity={isUp ? 1 : 0.85}
4039
4094
  >
4040
- <title>{`O:${cd.o} H:${cd.h} L:${cd.l} C:${cd.c}`}</title>
4095
+ <字の見出し k="readout.candlestick.title" v={{ o: cd.o, h: cd.h, l: cd.l, c: cd.c }} />
4041
4096
  </rect>
4042
4097
  </g>
4043
4098
  );
@@ -4089,7 +4144,7 @@ function VennReadout({
4089
4144
  <div className="cdl-ip-item cdl-ip-readout-venn" data-cdl-readout={id}>
4090
4145
  <span className="cdl-ip-label">{label}</span>
4091
4146
  {sizeA === 0 && sizeB === 0 ? (
4092
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4147
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4093
4148
  ) : (
4094
4149
  <svg className="cdl-ip-readout-venn-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4095
4150
  <circle cx={cxA} cy={cy} r={rA} fill={colorA} fillOpacity={0.4} stroke={colorA} strokeWidth={1.5} />
@@ -4158,13 +4213,13 @@ function SlopeReadout({
4158
4213
  <div className="cdl-ip-item cdl-ip-readout-slope" data-cdl-readout={id}>
4159
4214
  <span className="cdl-ip-label">{label}</span>
4160
4215
  {slopes.length === 0 ? (
4161
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4216
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4162
4217
  ) : (
4163
4218
  <svg className="cdl-ip-readout-slope-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4164
4219
  <line x1={xL} x2={xL} y1={pad - 4} y2={viewH - pad + 4} stroke="rgba(148,163,184,0.4)" />
4165
4220
  <line x1={xR} x2={xR} y1={pad - 4} y2={viewH - pad + 4} stroke="rgba(148,163,184,0.4)" />
4166
- <text x={xL} y={viewH - 4} fontSize={9} textAnchor="middle" fill="var(--v4-mute, #64748b)">before</text>
4167
- <text x={xR} y={viewH - 4} fontSize={9} textAnchor="middle" fill="var(--v4-mute, #64748b)">after</text>
4221
+ <text x={xL} y={viewH - 4} fontSize={9} textAnchor="middle" fill="var(--v4-mute, #64748b)"><字 k="readout.slope.before" /></text>
4222
+ <text x={xR} y={viewH - 4} fontSize={9} textAnchor="middle" fill="var(--v4-mute, #64748b)"><字 k="readout.slope.after" /></text>
4168
4223
  {slopes.map((s, i) => {
4169
4224
  const yB = yFor(s.before);
4170
4225
  const yA = yFor(s.after);
@@ -4229,7 +4284,7 @@ function FunnelReadout({
4229
4284
  <div className="cdl-ip-item cdl-ip-readout-funnel" data-cdl-readout={id}>
4230
4285
  <span className="cdl-ip-label">{label}</span>
4231
4286
  {stages.length === 0 ? (
4232
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4287
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4233
4288
  ) : (
4234
4289
  <svg className="cdl-ip-readout-funnel-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4235
4290
  {stages.map((s, i) => {
@@ -4312,7 +4367,7 @@ function GanttReadout({
4312
4367
  <div className="cdl-ip-item cdl-ip-readout-gantt" data-cdl-readout={id}>
4313
4368
  <span className="cdl-ip-label">{label}</span>
4314
4369
  {tasks.length === 0 ? (
4315
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4370
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4316
4371
  ) : (
4317
4372
  <svg className="cdl-ip-readout-gantt-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4318
4373
  {tasks.map((t, i) => {
@@ -4422,7 +4477,7 @@ function TreemapReadout({
4422
4477
  <div className="cdl-ip-item cdl-ip-readout-treemap" data-cdl-readout={id}>
4423
4478
  <span className="cdl-ip-label">{label}</span>
4424
4479
  {rects.length === 0 ? (
4425
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4480
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4426
4481
  ) : (
4427
4482
  <svg className="cdl-ip-readout-treemap-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4428
4483
  {rects.map((r, i) => (
@@ -4516,7 +4571,7 @@ function SankeyReadout({
4516
4571
  <div className="cdl-ip-item cdl-ip-readout-sankey" data-cdl-readout={id}>
4517
4572
  <span className="cdl-ip-label">{label}</span>
4518
4573
  {flows.length === 0 ? (
4519
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4574
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4520
4575
  ) : (
4521
4576
  <svg className="cdl-ip-readout-sankey-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4522
4577
  {/* left bars */}
@@ -4609,7 +4664,7 @@ function PolarAreaReadout({
4609
4664
  <div className="cdl-ip-item cdl-ip-readout-polar-area" data-cdl-readout={id}>
4610
4665
  <span className="cdl-ip-label">{label}</span>
4611
4666
  {n === 0 ? (
4612
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4667
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4613
4668
  ) : (
4614
4669
  <svg className="cdl-ip-readout-polar-area-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4615
4670
  {[0.33, 0.66, 1].map((t) => (
@@ -4679,7 +4734,7 @@ function StepIndicatorReadout({
4679
4734
  <div className="cdl-ip-item cdl-ip-readout-step-indicator" data-cdl-readout={id}>
4680
4735
  <span className="cdl-ip-label">{label}</span>
4681
4736
  {n === 0 ? (
4682
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4737
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4683
4738
  ) : (
4684
4739
  <svg className="cdl-ip-readout-step-indicator-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
4685
4740
  {n > 1
@@ -4783,10 +4838,10 @@ function BulletChartReadout({
4783
4838
  <rect x={badX} y={pad} width={avgX - badX} height={trackH} fill="rgba(234,179,8,0.25)" />
4784
4839
  <rect x={avgX} y={pad} width={pad + w - avgX} height={trackH} fill="rgba(34,197,94,0.25)" />
4785
4840
  <rect x={pad} y={pad + trackH * 0.3} width={actualX - pad} height={trackH * 0.4} fill={colorActual} rx={2}>
4786
- <title>{`actual: ${safeActual}`}</title>
4841
+ <字の見出し k="readout.bulletChart.actual" v={{ v: safeActual }} />
4787
4842
  </rect>
4788
4843
  <line x1={targetX} x2={targetX} y1={pad - 2} y2={pad + trackH + 2} stroke="#0f172a" strokeWidth={2}>
4789
- <title>{`target: ${safeTarget}`}</title>
4844
+ <字の見出し k="readout.bulletChart.target" v={{ v: safeTarget }} />
4790
4845
  </line>
4791
4846
  </svg>
4792
4847
  </div>
@@ -4865,7 +4920,7 @@ function LeaderboardReadout({
4865
4920
  <div className="cdl-ip-item cdl-ip-readout-leaderboard" data-cdl-readout={id}>
4866
4921
  <span className="cdl-ip-label">{label}</span>
4867
4922
  {sorted.length === 0 ? (
4868
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
4923
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4869
4924
  ) : (
4870
4925
  <ol className="cdl-ip-readout-leaderboard-list">
4871
4926
  {sorted.map((it, i) => {
@@ -4938,7 +4993,8 @@ function TrafficLightReadout({
4938
4993
  filter: isActive ? `drop-shadow(0 0 6px ${colors[i]})` : undefined,
4939
4994
  }}
4940
4995
  >
4941
- <title>{isActive ? ["red", "yellow", "green"][i] : ""}</title>
4996
+ {/* 灯の見出しだけを辞書で引く。 値は `red` / `yellow` / `amber` / `green` の綴りで読む */}
4997
+ <字の見出し k={isActive ? (["readout.trafficLight.red", "readout.trafficLight.yellow", "readout.trafficLight.green"] as const)[i] : undefined} />
4942
4998
  </circle>
4943
4999
  );
4944
5000
  })}
@@ -4989,7 +5045,7 @@ function TagCloudReadout({
4989
5045
  <div className="cdl-ip-item cdl-ip-readout-tag-cloud" data-cdl-readout={id}>
4990
5046
  <span className="cdl-ip-label">{label}</span>
4991
5047
  {tags.length === 0 ? (
4992
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
5048
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
4993
5049
  ) : (
4994
5050
  <div className="cdl-ip-readout-tag-cloud-list">
4995
5051
  {tags.map((t, i) => (
@@ -5043,7 +5099,7 @@ function ActivityFeedReadout({
5043
5099
  <div className="cdl-ip-item cdl-ip-readout-activity-feed" data-cdl-readout={id}>
5044
5100
  <span className="cdl-ip-label">{label}</span>
5045
5101
  {shown.length === 0 ? (
5046
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
5102
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
5047
5103
  ) : (
5048
5104
  <ul className="cdl-ip-readout-activity-feed-list">
5049
5105
  {shown.map((e, i) => (
@@ -5110,9 +5166,9 @@ function RatingReadout({
5110
5166
  strokeWidth={1}
5111
5167
  />
5112
5168
  {isEmpty ? (
5113
- <title>{`${i + 1}: empty`}</title>
5169
+ <字の見出し 前={`${i + 1}: `} k="readout.rating.empty" />
5114
5170
  ) : (
5115
- <title>{`${i + 1}: ${isFull ? "full" : "half"}`}</title>
5171
+ <字の見出し 前={`${i + 1}: `} k={isFull ? "readout.rating.full" : "readout.rating.half"} />
5116
5172
  )}
5117
5173
  </svg>
5118
5174
  );
@@ -5239,7 +5295,7 @@ function ChatBubbleReadout({
5239
5295
  <div className="cdl-ip-item cdl-ip-readout-chat-bubble" data-cdl-readout={id}>
5240
5296
  <span className="cdl-ip-label">{label}</span>
5241
5297
  {shown.length === 0 ? (
5242
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
5298
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
5243
5299
  ) : (
5244
5300
  <ul className="cdl-ip-readout-chat-bubble-list">
5245
5301
  {shown.map((m, i) => (
@@ -5357,7 +5413,7 @@ function ChecklistReadout({
5357
5413
  <div className="cdl-ip-item cdl-ip-readout-checklist" data-cdl-readout={id}>
5358
5414
  <span className="cdl-ip-label">{label}</span>
5359
5415
  {items.length === 0 ? (
5360
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
5416
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
5361
5417
  ) : (
5362
5418
  <>
5363
5419
  <div className="cdl-ip-readout-checklist-progress">
@@ -5494,7 +5550,7 @@ function PriceTagReadout({
5494
5550
  ) : null}
5495
5551
  </div>
5496
5552
  {savings > 0 ? (
5497
- <span className="cdl-ip-readout-price-tag-savings">You save {currency}{savings}</span>
5553
+ <span className="cdl-ip-readout-price-tag-savings"><字 k="readout.priceTag.save" v={{ amount: `${currency}${savings}` }} /></span>
5498
5554
  ) : null}
5499
5555
  </div>
5500
5556
  );
@@ -5646,6 +5702,13 @@ function StopwatchReadout({
5646
5702
  );
5647
5703
  }
5648
5704
 
5705
+ /** `confidence-meter` の帯の綴り (`data-cdl-band` の値) から、表示の字の鍵を引く */
5706
+ const 確信度の帯の字 = {
5707
+ low: "readout.confidence.low",
5708
+ mid: "readout.confidence.mid",
5709
+ high: "readout.confidence.high",
5710
+ } as const satisfies Record<string, CdlTextKey>;
5711
+
5649
5712
  function ConfidenceMeterReadout({
5650
5713
  id,
5651
5714
  label,
@@ -5687,7 +5750,8 @@ function ConfidenceMeterReadout({
5687
5750
  {Math.round(safeValue)}%
5688
5751
  </span>
5689
5752
  <span className="cdl-ip-readout-confidence-meter-band" style={{ color: bandColor }}>
5690
- {bandLabel}
5753
+ {/* 表示の字だけを引く。 `data-cdl-band` は英語の綴りのまま */}
5754
+ <字 k={確信度の帯の字[bandLabel as keyof typeof 確信度の帯の字]} />
5691
5755
  </span>
5692
5756
  </div>
5693
5757
  <svg className="cdl-ip-readout-confidence-meter-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
@@ -5735,7 +5799,7 @@ function ReactionBarReadout({
5735
5799
  <div className="cdl-ip-item cdl-ip-readout-reaction-bar" data-cdl-readout={id}>
5736
5800
  <span className="cdl-ip-label">{label}</span>
5737
5801
  {reactions.length === 0 ? (
5738
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
5802
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
5739
5803
  ) : (
5740
5804
  <div className="cdl-ip-readout-reaction-bar-row">
5741
5805
  {reactions.map((r, i) => (
@@ -5788,7 +5852,7 @@ function PillGroupReadout({
5788
5852
  <div className="cdl-ip-item cdl-ip-readout-pill-group" data-cdl-readout={id}>
5789
5853
  <span className="cdl-ip-label">{label}</span>
5790
5854
  {pills.length === 0 ? (
5791
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
5855
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
5792
5856
  ) : (
5793
5857
  <div className="cdl-ip-readout-pill-group-row">
5794
5858
  {pills.map((p, i) => (
@@ -5902,7 +5966,7 @@ function MetricsGridReadout({
5902
5966
  <div className="cdl-ip-item cdl-ip-readout-metrics-grid" data-cdl-readout={id}>
5903
5967
  <span className="cdl-ip-label">{label}</span>
5904
5968
  {items.length === 0 ? (
5905
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
5969
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
5906
5970
  ) : (
5907
5971
  <div className="cdl-ip-readout-metrics-grid-grid">
5908
5972
  {items.map((m, i) => (
@@ -6014,7 +6078,7 @@ function IconTileReadout({
6014
6078
  <div className="cdl-ip-item cdl-ip-readout-icon-tile" data-cdl-readout={id}>
6015
6079
  <span className="cdl-ip-label">{label}</span>
6016
6080
  {tiles.length === 0 ? (
6017
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6081
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6018
6082
  ) : (
6019
6083
  <div className="cdl-ip-readout-icon-tile-row">
6020
6084
  {tiles.map((t, i) => (
@@ -6075,7 +6139,7 @@ function TokenListReadout({
6075
6139
  <div className="cdl-ip-item cdl-ip-readout-token-list" data-cdl-readout={id}>
6076
6140
  <span className="cdl-ip-label">{label}</span>
6077
6141
  {tokens.length === 0 ? (
6078
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6142
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6079
6143
  ) : (
6080
6144
  <ul className="cdl-ip-readout-token-list-items">
6081
6145
  {tokens.map((t, i) => {
@@ -6149,7 +6213,7 @@ function MapPinReadout({
6149
6213
  <div className="cdl-ip-item cdl-ip-readout-map-pin" data-cdl-readout={id}>
6150
6214
  <span className="cdl-ip-label">{label}</span>
6151
6215
  {pins.length === 0 ? (
6152
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6216
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6153
6217
  ) : (
6154
6218
  <svg className="cdl-ip-readout-map-pin-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
6155
6219
  <rect x={0} y={0} width={viewW} height={viewH} fill="rgba(148,163,184,0.08)" stroke="rgba(148,163,184,0.3)" strokeWidth={1} rx={4} />
@@ -6194,10 +6258,11 @@ function PriorityBadgeReadout({
6194
6258
  let level = "med";
6195
6259
  if (p === "high" || p === "2") level = "high";
6196
6260
  else if (p === "low" || p === "0") level = "low";
6197
- const spec: Record<string, { icon: string; color: string; bg: string; text: string }> = {
6198
- high: { icon: "▲", color: "#ef4444", bg: "rgba(239,68,68,0.12)", text: "HIGH" },
6199
- med: { icon: "●", color: "#eab308", bg: "rgba(234,179,8,0.12)", text: "MED" },
6200
- low: { icon: "▼", color: "#64748b", bg: "rgba(100,116,139,0.12)", text: "LOW" },
6261
+ // 値は綴り (`high` / `2` / `low` / `0`) で読み、表示の字だけを辞書で引く (#849)。 `data-cdl-priority` は綴りのまま
6262
+ const spec: Record<string, { icon: string; color: string; bg: string; text: CdlTextKey }> = {
6263
+ high: { icon: "▲", color: "#ef4444", bg: "rgba(239,68,68,0.12)", text: "readout.priority.high" },
6264
+ med: { icon: "●", color: "#eab308", bg: "rgba(234,179,8,0.12)", text: "readout.priority.med" },
6265
+ low: { icon: "▼", color: "#64748b", bg: "rgba(100,116,139,0.12)", text: "readout.priority.low" },
6201
6266
  };
6202
6267
  const cur = spec[level]!;
6203
6268
  return (
@@ -6209,7 +6274,7 @@ function PriorityBadgeReadout({
6209
6274
  style={{ background: cur.bg, borderColor: cur.color, color: cur.color }}
6210
6275
  >
6211
6276
  <span className="cdl-ip-readout-priority-badge-icon">{cur.icon}</span>
6212
- <span>{cur.text}</span>
6277
+ <span><字 k={cur.text} /></span>
6213
6278
  </span>
6214
6279
  {text ? <span className="cdl-ip-readout-priority-badge-text">{text}</span> : null}
6215
6280
  </div>
@@ -6258,7 +6323,7 @@ function PodiumReadout({
6258
6323
  <div className="cdl-ip-item cdl-ip-readout-podium" data-cdl-readout={id}>
6259
6324
  <span className="cdl-ip-label">{label}</span>
6260
6325
  {podium.length === 0 ? (
6261
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6326
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6262
6327
  ) : (
6263
6328
  <svg className="cdl-ip-readout-podium-svg" viewBox={`0 0 ${viewW} ${viewH}`} width={viewW} height={viewH}>
6264
6329
  {order.map((rankIdx, colIdx) => {
@@ -6328,7 +6393,7 @@ function PollBarReadout({
6328
6393
  <div className="cdl-ip-item cdl-ip-readout-poll-bar" data-cdl-readout={id}>
6329
6394
  <span className="cdl-ip-label">{label}</span>
6330
6395
  {options.length === 0 ? (
6331
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6396
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6332
6397
  ) : (
6333
6398
  <ul className="cdl-ip-readout-poll-bar-list">
6334
6399
  {options.map((o, i) => {
@@ -6375,6 +6440,7 @@ function UserStackReadout({
6375
6440
  max: number;
6376
6441
  size: number;
6377
6442
  }): JSX.Element {
6443
+ const 引く = use字引();
6378
6444
  const names = safeParseArray(value).map(String);
6379
6445
  const shown = names.slice(0, max);
6380
6446
  const overflow = names.length > max;
@@ -6391,7 +6457,7 @@ function UserStackReadout({
6391
6457
  <div className="cdl-ip-item cdl-ip-readout-user-stack" data-cdl-readout={id}>
6392
6458
  <span className="cdl-ip-label">{label}</span>
6393
6459
  {shown.length === 0 ? (
6394
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6460
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6395
6461
  ) : (
6396
6462
  <div className="cdl-ip-readout-user-stack-row">
6397
6463
  {shown.map((name, i) => (
@@ -6420,7 +6486,7 @@ function UserStackReadout({
6420
6486
  fontSize: `${Math.round(size * 0.35)}px`,
6421
6487
  marginLeft: `-${size * 0.3}px`,
6422
6488
  }}
6423
- title={`+${overflowCount} more`}
6489
+ title={引く("readout.more", { n: overflowCount })}
6424
6490
  >
6425
6491
  +{overflowCount}
6426
6492
  </span>
@@ -6466,7 +6532,7 @@ function CommitListReadout({
6466
6532
  <div className="cdl-ip-item cdl-ip-readout-commit-list" data-cdl-readout={id}>
6467
6533
  <span className="cdl-ip-label">{label}</span>
6468
6534
  {shown.length === 0 ? (
6469
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6535
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6470
6536
  ) : (
6471
6537
  <ul className="cdl-ip-readout-commit-list-items">
6472
6538
  {shown.map((c, i) => (
@@ -6571,7 +6637,7 @@ function EventLogReadout({
6571
6637
  <div className="cdl-ip-item cdl-ip-readout-event-log" data-cdl-readout={id}>
6572
6638
  <span className="cdl-ip-label">{label}</span>
6573
6639
  {shown.length === 0 ? (
6574
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6640
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6575
6641
  ) : (
6576
6642
  <ul className="cdl-ip-readout-event-log-items">
6577
6643
  {shown.map((e, i) => {
@@ -6631,7 +6697,7 @@ function SearchResultReadout({
6631
6697
  <div className="cdl-ip-item cdl-ip-readout-search-result" data-cdl-readout={id}>
6632
6698
  <span className="cdl-ip-label">{label}</span>
6633
6699
  {shown.length === 0 ? (
6634
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6700
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6635
6701
  ) : (
6636
6702
  <ul className="cdl-ip-readout-search-result-items">
6637
6703
  {shown.map((h, i) => (
@@ -6683,7 +6749,7 @@ function RoadmapReadout({
6683
6749
  <div className="cdl-ip-item cdl-ip-readout-roadmap" data-cdl-readout={id} style={{ minWidth: `${viewW}px` }}>
6684
6750
  <span className="cdl-ip-label">{label}</span>
6685
6751
  {quarters.length === 0 ? (
6686
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6752
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6687
6753
  ) : (
6688
6754
  <div className="cdl-ip-readout-roadmap-row" style={{ minHeight: `${viewH}px` }}>
6689
6755
  {quarters.map((q, i) => (
@@ -6740,7 +6806,7 @@ function WeatherForecastReadout({
6740
6806
  <div className="cdl-ip-item cdl-ip-readout-weather-forecast" data-cdl-readout={id}>
6741
6807
  <span className="cdl-ip-label">{label}</span>
6742
6808
  {days.length === 0 ? (
6743
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6809
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6744
6810
  ) : (
6745
6811
  <div className="cdl-ip-readout-weather-forecast-row">
6746
6812
  {days.map((d, i) => (
@@ -6796,7 +6862,7 @@ function VideoCardReadout({
6796
6862
  <div className="cdl-ip-item cdl-ip-readout-video-card" data-cdl-readout={id}>
6797
6863
  <span className="cdl-ip-label">{label}</span>
6798
6864
  {shown.length === 0 ? (
6799
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6865
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6800
6866
  ) : (
6801
6867
  <ul className="cdl-ip-readout-video-card-list">
6802
6868
  {shown.map((v, i) => (
@@ -6807,7 +6873,7 @@ function VideoCardReadout({
6807
6873
  </div>
6808
6874
  <div className="cdl-ip-readout-video-card-info">
6809
6875
  <div className="cdl-ip-readout-video-card-title">{v.title}</div>
6810
- <div className="cdl-ip-readout-video-card-views">{v.views} views</div>
6876
+ <div className="cdl-ip-readout-video-card-views"><字 k="readout.videoCard.views" v={{ v: v.views }} /></div>
6811
6877
  </div>
6812
6878
  </li>
6813
6879
  ))}
@@ -6837,7 +6903,7 @@ function OrderStatusReadout({
6837
6903
  <div className="cdl-ip-item cdl-ip-readout-order-status" data-cdl-readout={id} data-cdl-step={safeCurrent}>
6838
6904
  <span className="cdl-ip-label">{label}</span>
6839
6905
  {stepArr.length === 0 ? (
6840
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6906
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6841
6907
  ) : (
6842
6908
  <ul className="cdl-ip-readout-order-status-list">
6843
6909
  {stepArr.map((s, i) => {
@@ -6903,12 +6969,12 @@ function AttendanceGridReadout({
6903
6969
  <div className="cdl-ip-item cdl-ip-readout-attendance-grid" data-cdl-readout={id}>
6904
6970
  <span className="cdl-ip-label">{label}</span>
6905
6971
  {rows.length === 0 || memberArr.length === 0 ? (
6906
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
6972
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6907
6973
  ) : (
6908
6974
  <table className="cdl-ip-readout-attendance-grid-table">
6909
6975
  <thead>
6910
6976
  <tr>
6911
- <th className="cdl-ip-readout-attendance-grid-corner">day</th>
6977
+ <th className="cdl-ip-readout-attendance-grid-corner"><字 k="readout.attendance.corner" /></th>
6912
6978
  {memberArr.map((m, i) => (
6913
6979
  <th key={i} className="cdl-ip-readout-attendance-grid-member">{m}</th>
6914
6980
  ))}
@@ -6978,7 +7044,7 @@ function TimezoneClockReadout({
6978
7044
  <div className="cdl-ip-item cdl-ip-readout-timezone-clock" data-cdl-readout={id}>
6979
7045
  <span className="cdl-ip-label">{label}</span>
6980
7046
  {clocks.length === 0 ? (
6981
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
7047
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
6982
7048
  ) : (
6983
7049
  <div className="cdl-ip-readout-timezone-clock-row">
6984
7050
  {clocks.map((c, i) => (
@@ -7027,7 +7093,7 @@ function FormSummaryReadout({
7027
7093
  <div className="cdl-ip-item cdl-ip-readout-form-summary" data-cdl-readout={id}>
7028
7094
  <span className="cdl-ip-label">{label}</span>
7029
7095
  {fields.length === 0 ? (
7030
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
7096
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
7031
7097
  ) : (
7032
7098
  <dl className="cdl-ip-readout-form-summary-list">
7033
7099
  {fields.map((f, i) => (
@@ -7079,7 +7145,7 @@ function SongQueueReadout({
7079
7145
  <div className="cdl-ip-item cdl-ip-readout-song-queue" data-cdl-readout={id} data-cdl-current={safeCurrent}>
7080
7146
  <span className="cdl-ip-label">{label}</span>
7081
7147
  {shown.length === 0 ? (
7082
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
7148
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
7083
7149
  ) : (
7084
7150
  <ol className="cdl-ip-readout-song-queue-list">
7085
7151
  {shown.map((s, i) => {
@@ -7142,12 +7208,20 @@ function CalendarMonthReadout({
7142
7208
  }
7143
7209
  }
7144
7210
  // build 6 x 7 grid (day of month の初日 offset は infer せず、 単純に days[i].day を敷き詰め)
7145
- const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
7211
+ const weekDays = [
7212
+ "readout.weekday.0",
7213
+ "readout.weekday.1",
7214
+ "readout.weekday.2",
7215
+ "readout.weekday.3",
7216
+ "readout.weekday.4",
7217
+ "readout.weekday.5",
7218
+ "readout.weekday.6",
7219
+ ] as const;
7146
7220
  return (
7147
7221
  <div className="cdl-ip-item cdl-ip-readout-calendar-month" data-cdl-readout={id}>
7148
7222
  <span className="cdl-ip-label">{label}</span>
7149
7223
  {days.length === 0 ? (
7150
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
7224
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
7151
7225
  ) : (
7152
7226
  <div className="cdl-ip-readout-calendar-month-widget">
7153
7227
  {monthName ? (
@@ -7155,7 +7229,7 @@ function CalendarMonthReadout({
7155
7229
  ) : null}
7156
7230
  <div className="cdl-ip-readout-calendar-month-weekdays">
7157
7231
  {weekDays.map((w) => (
7158
- <span key={w} className="cdl-ip-readout-calendar-month-weekday">{w}</span>
7232
+ <span key={w} className="cdl-ip-readout-calendar-month-weekday"><字 k={w} /></span>
7159
7233
  ))}
7160
7234
  </div>
7161
7235
  <div className="cdl-ip-readout-calendar-month-grid">
@@ -7213,7 +7287,7 @@ function TerminalReadout({
7213
7287
  <div className="cdl-ip-item cdl-ip-readout-terminal" data-cdl-readout={id}>
7214
7288
  <span className="cdl-ip-label">{label}</span>
7215
7289
  {shown.length === 0 ? (
7216
- <span className="cdl-ip-readout-array-bar-empty">empty</span>
7290
+ <span className="cdl-ip-readout-array-bar-empty"><字 k="readout.empty" /></span>
7217
7291
  ) : (
7218
7292
  <div className="cdl-ip-readout-terminal-window">
7219
7293
  <div className="cdl-ip-readout-terminal-titlebar">
@@ -7333,15 +7407,12 @@ function KanbanBoardReadout({
7333
7407
  max: number;
7334
7408
  }): JSX.Element {
7335
7409
  type Task = { title: string; priority?: string };
7336
- const columns: { key: string; label: string; color: string; tasks: Task[] } = {
7337
- todo: { key: "todo", label: "Todo", color: "#94a3b8", tasks: [] },
7338
- inprogress: { key: "inprogress", label: "In Progress", color: "#2563eb", tasks: [] },
7339
- done: { key: "done", label: "Done", color: "#22c55e", tasks: [] },
7340
- } as unknown as { key: string; label: string; color: string; tasks: Task[] };
7410
+ const 引く = use字引();
7411
+ // 列は綴り (`todo` / `inprogress` / `done`) で振り分け、見出しの字だけを辞書で引く (#849)
7341
7412
  const buckets: Record<string, { key: string; label: string; color: string; tasks: Task[] }> = {
7342
- todo: { key: "todo", label: "Todo", color: "#94a3b8", tasks: [] },
7343
- inprogress: { key: "inprogress", label: "In Progress", color: "#2563eb", tasks: [] },
7344
- done: { key: "done", label: "Done", color: "#22c55e", tasks: [] },
7413
+ todo: { key: "todo", label: 引く("readout.kanban.todo"), color: "#94a3b8", tasks: [] },
7414
+ inprogress: { key: "inprogress", label: 引く("readout.kanban.inprogress"), color: "#2563eb", tasks: [] },
7415
+ done: { key: "done", label: 引く("readout.kanban.done"), color: "#22c55e", tasks: [] },
7345
7416
  };
7346
7417
  const trimmed = value.trim();
7347
7418
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
@@ -7362,7 +7433,6 @@ function KanbanBoardReadout({
7362
7433
  // silent skip
7363
7434
  }
7364
7435
  }
7365
- void columns;
7366
7436
  const order = ["todo", "inprogress", "done"] as const;
7367
7437
  const rowH = 26;
7368
7438
  const headerH = 28;
@@ -7454,7 +7524,7 @@ function KanbanBoardReadout({
7454
7524
  fill="#64748b"
7455
7525
  style={{ userSelect: "none" }}
7456
7526
  >
7457
- +{overflow} more
7527
+ <字 k="readout.more" v={{ n: overflow }} />
7458
7528
  </text>
7459
7529
  ) : null}
7460
7530
  </g>
@@ -8056,7 +8126,7 @@ function UserPresenceReadout({
8056
8126
  fill="#64748b"
8057
8127
  style={{ userSelect: "none" }}
8058
8128
  >
8059
- +{overflow} more
8129
+ <字 k="readout.more" v={{ n: overflow }} />
8060
8130
  </text>
8061
8131
  ) : null}
8062
8132
  </svg>
@@ -8113,7 +8183,7 @@ function RatingThumbReadout({
8113
8183
  ▲ {upCount}
8114
8184
  </text>
8115
8185
  <text x={width / 2} y={20} fontSize={11} textAnchor="middle" fill="#64748b" style={{ userSelect: "none" }}>
8116
- {upPct}% up · {total} votes
8186
+ <字 k="readout.ratingThumb.summary" v={{ pct: upPct, total }} />
8117
8187
  </text>
8118
8188
  <text
8119
8189
  x={width - 8}
@@ -8300,8 +8370,7 @@ function KpiTrendTileReadout({
8300
8370
  {unit} ({deltaPct.toFixed(1)}%)
8301
8371
  </text>
8302
8372
  <text x={12} y={78} fontSize={10} fill="#64748b" style={{ userSelect: "none" }}>
8303
- prev {prev}
8304
- {unit}
8373
+ <字 k="readout.kpiTrend.prev" v={{ v: `${prev}${unit}` }} />
8305
8374
  </text>
8306
8375
  {history.length > 1 ? (
8307
8376
  <polyline points={points} fill="none" stroke={arrowColor} strokeWidth={2} />
@@ -8401,7 +8470,7 @@ function QuickPollEmojiReadout({
8401
8470
  fill="#64748b"
8402
8471
  style={{ userSelect: "none" }}
8403
8472
  >
8404
- total {total}
8473
+ <字 k="readout.poll.total" v={{ n: total }} />
8405
8474
  </text>
8406
8475
  </svg>
8407
8476
  </div>
@@ -8544,10 +8613,10 @@ function ThreadSummaryReadout({
8544
8613
  >
8545
8614
  <rect x={0} y={0} width={width} height={height} fill="#f8fafc" rx={6} />
8546
8615
  <text x={12} y={22} fontSize={12} fontWeight={600} fill="#0f172a" style={{ userSelect: "none" }}>
8547
- {participants} participants
8616
+ <字 k="readout.thread.participants" v={{ n: participants }} />
8548
8617
  </text>
8549
8618
  <text x={12} y={42} fontSize={11} fill="#334155" style={{ userSelect: "none" }}>
8550
- last: {lastAuthor}
8619
+ <字 k="readout.thread.last" v={{ name: lastAuthor }} />
8551
8620
  </text>
8552
8621
  <text x={12} y={60} fontSize={10} fill="#64748b" style={{ userSelect: "none" }}>
8553
8622
  {timeAgo}
@@ -8587,7 +8656,7 @@ function ReadReceiptReadout({
8587
8656
  colorPending: string;
8588
8657
  }): JSX.Element {
8589
8658
  const status = Number.isFinite(value) ? Math.max(0, Math.min(2, Math.floor(value))) : 0;
8590
- const labelText = status === 0 ? "sent" : status === 1 ? "delivered" : "read";
8659
+ const labelKey = status === 0 ? "readout.readReceipt.sent" : status === 1 ? "readout.readReceipt.delivered" : "readout.readReceipt.read";
8591
8660
  const iconColor = status === 2 ? colorRead : colorPending;
8592
8661
  const width = 180;
8593
8662
  const height = 32;
@@ -8618,7 +8687,7 @@ function ReadReceiptReadout({
8618
8687
  ) : null}
8619
8688
  </g>
8620
8689
  <text x={52} y={height / 2 + 4} fontSize={11} fontWeight={600} fill={iconColor} style={{ userSelect: "none" }}>
8621
- {labelText}
8690
+ <字 k={labelKey} />
8622
8691
  </text>
8623
8692
  </svg>
8624
8693
  </div>
@@ -8639,7 +8708,7 @@ function PasswordStrengthReadout({
8639
8708
  colorWeak: string;
8640
8709
  }): JSX.Element {
8641
8710
  const level = Number.isFinite(value) ? Math.max(0, Math.min(4, Math.floor(value))) : 0;
8642
- const labels = ["too weak", "weak", "fair", "good", "strong"];
8711
+ const labels = ["readout.password.0", "readout.password.1", "readout.password.2", "readout.password.3", "readout.password.4"] as const;
8643
8712
  const colors = [colorWeak, "#f97316", "#eab308", "#84cc16", colorStrong];
8644
8713
  const barW = 46;
8645
8714
  const gap = 6;
@@ -8680,7 +8749,7 @@ function PasswordStrengthReadout({
8680
8749
  fill={level > 0 ? colors[level] ?? colorStrong : "#94a3b8"}
8681
8750
  style={{ userSelect: "none" }}
8682
8751
  >
8683
- {labels[level]}
8752
+ <字 k={labels[level]!} />
8684
8753
  </text>
8685
8754
  </svg>
8686
8755
  </div>
@@ -8821,7 +8890,7 @@ function FileDropzoneReadout({
8821
8890
  {filename.length > 26 ? `${filename.slice(0, 25)}…` : filename}
8822
8891
  </text>
8823
8892
  <text x={54} y={50} fontSize={10} fill="#64748b" style={{ userSelect: "none" }}>
8824
- uploaded
8893
+ <字 k="readout.dropzone.uploaded" />
8825
8894
  </text>
8826
8895
  </g>
8827
8896
  ) : (
@@ -8835,7 +8904,7 @@ function FileDropzoneReadout({
8835
8904
  fill="#64748b"
8836
8905
  style={{ userSelect: "none" }}
8837
8906
  >
8838
- ⬆ Drop file here
8907
+ <字 k="readout.dropzone.drop" />
8839
8908
  </text>
8840
8909
  <text
8841
8910
  x={width / 2}
@@ -8845,7 +8914,7 @@ function FileDropzoneReadout({
8845
8914
  fill="#94a3b8"
8846
8915
  style={{ userSelect: "none" }}
8847
8916
  >
8848
- or click to browse
8917
+ <字 k="readout.dropzone.browse" />
8849
8918
  </text>
8850
8919
  </g>
8851
8920
  )}
@@ -8886,7 +8955,7 @@ function LogStreamReadout({
8886
8955
  }
8887
8956
  }
8888
8957
  const shown = rows.slice(-5);
8889
- const levelLabels = ["DBG", "INF", "WRN", "ERR"];
8958
+ const levelLabels = ["readout.log.0", "readout.log.1", "readout.log.2", "readout.log.3"] as const;
8890
8959
  const levelColors = ["#94a3b8", "#2563eb", "#f97316", "#ef4444"];
8891
8960
  const width = 300;
8892
8961
  const rowH = 18;
@@ -8920,7 +8989,7 @@ function LogStreamReadout({
8920
8989
  fontFamily="monospace"
8921
8990
  style={{ userSelect: "none" }}
8922
8991
  >
8923
- {levelLabels[r.level] ?? "INF"}
8992
+ <字 k={levelLabels[r.level] ?? "readout.log.1"} />
8924
8993
  </text>
8925
8994
  <text x={94} y={y + 12} fontSize={10} fill="#e2e8f0" fontFamily="monospace" style={{ userSelect: "none" }}>
8926
8995
  <title>{r.msg}</title>
@@ -9120,11 +9189,11 @@ function CartSummaryReadout({
9120
9189
  if (!Number.isFinite(subtotal)) subtotal = 0;
9121
9190
  if (!Number.isFinite(shipping)) shipping = 0;
9122
9191
  if (!Number.isFinite(total)) total = 0;
9123
- const rows = [
9124
- { l: "Items", v: `${items}`, bold: false },
9125
- { l: "Subtotal", v: `${currency}${subtotal.toFixed(2)}`, bold: false },
9126
- { l: "Shipping", v: `${currency}${shipping.toFixed(2)}`, bold: false },
9127
- { l: "Total", v: `${currency}${total.toFixed(2)}`, bold: true },
9192
+ const rows: { l: CdlTextKey; v: string; bold: boolean }[] = [
9193
+ { l: "readout.cart.items", v: `${items}`, bold: false },
9194
+ { l: "readout.cart.subtotal", v: `${currency}${subtotal.toFixed(2)}`, bold: false },
9195
+ { l: "readout.cart.shipping", v: `${currency}${shipping.toFixed(2)}`, bold: false },
9196
+ { l: "readout.cart.total", v: `${currency}${total.toFixed(2)}`, bold: true },
9128
9197
  ];
9129
9198
  const width = 260;
9130
9199
  const rowH = 20;
@@ -9155,7 +9224,7 @@ function CartSummaryReadout({
9155
9224
  fill={isTotal ? colorTotal : "#334155"}
9156
9225
  style={{ userSelect: "none" }}
9157
9226
  >
9158
- {r.l}
9227
+ <字 k={r.l} />
9159
9228
  </text>
9160
9229
  <text
9161
9230
  x={width - 12}
@@ -9208,6 +9277,9 @@ function PricingTierReadout({
9208
9277
  }
9209
9278
  }
9210
9279
  if (!Number.isFinite(price)) price = 0;
9280
+ const 引く = use字引();
9281
+ // 名前が空の時の既定の名前。 見出しと押す札の 2 か所で使う
9282
+ const 見せる名前 = planName || 引く("readout.pricing.plan");
9211
9283
  const width = 240;
9212
9284
  const height = 176;
9213
9285
  return (
@@ -9230,7 +9302,7 @@ function PricingTierReadout({
9230
9302
  fill="#ffffff"
9231
9303
  style={{ userSelect: "none" }}
9232
9304
  >
9233
- {planName || "Plan"}
9305
+ {見せる名前}
9234
9306
  </text>
9235
9307
  <text
9236
9308
  x={width / 2}
@@ -9252,7 +9324,7 @@ function PricingTierReadout({
9252
9324
  fill="#64748b"
9253
9325
  style={{ userSelect: "none" }}
9254
9326
  >
9255
- per month
9327
+ <字 k="readout.pricing.perMonth" />
9256
9328
  </text>
9257
9329
  {features.slice(0, 3).map((f, i) => {
9258
9330
  const y = 96 + i * 18;
@@ -9279,7 +9351,7 @@ function PricingTierReadout({
9279
9351
  fill="#ffffff"
9280
9352
  style={{ userSelect: "none" }}
9281
9353
  >
9282
- Get {planName || "Plan"}
9354
+ {引く("readout.pricing.get", { name: 見せる名前 })}
9283
9355
  </text>
9284
9356
  </svg>
9285
9357
  </div>
@@ -9345,7 +9417,7 @@ function CouponCodeReadout({
9345
9417
  fontFamily="monospace"
9346
9418
  style={{ userSelect: "none" }}
9347
9419
  >
9348
- {code || "ENTER CODE"}
9420
+ {code || <字 k="readout.coupon.placeholder" />}
9349
9421
  </text>
9350
9422
  {applied ? (
9351
9423
  <g data-cdl-coupon-applied="1">
@@ -9359,7 +9431,7 @@ function CouponCodeReadout({
9359
9431
  fill="#ffffff"
9360
9432
  style={{ userSelect: "none" }}
9361
9433
  >
9362
- -{discountPct}% off
9434
+ <字 k="readout.coupon.off" v={{ pct: discountPct }} />
9363
9435
  </text>
9364
9436
  </g>
9365
9437
  ) : (
@@ -9374,7 +9446,7 @@ function CouponCodeReadout({
9374
9446
  fill="#ffffff"
9375
9447
  style={{ userSelect: "none" }}
9376
9448
  >
9377
- Apply
9449
+ <字 k="readout.coupon.apply" />
9378
9450
  </text>
9379
9451
  </g>
9380
9452
  )}
@@ -9456,7 +9528,7 @@ function ArticlePreviewReadout({
9456
9528
  {excerpt.length > 56 ? `${excerpt.slice(28, 55)}…` : excerpt.slice(28, 56)}
9457
9529
  </text>
9458
9530
  <text x={thumbW + 20} y={height - 14} fontSize={10} fontWeight={600} fill="#64748b" style={{ userSelect: "none" }}>
9459
- by {author}
9531
+ <字 k="readout.article.by" v={{ author }} />
9460
9532
  </text>
9461
9533
  <text x={width - 8} y={height - 14} fontSize={10} textAnchor="end" fill="#94a3b8" style={{ userSelect: "none" }}>
9462
9534
  {timeAgo}