chore: redesign article localization before implementation
This commit is contained in:
1 parent
99a74dc3a2
commit
8c4a1825ab
31 files changed
+3365
-882
No files matched your search
@@ -20,7 +20,7 @@
|
||||
<div style="display:flex;align-items:flex-end;gap:32px;max-width:1400px">
|
||||
<div style="flex:1;display:flex;flex-direction:column;gap:10px">
|
||||
<h1 style="margin:0;font:600 36px/44px 'IBM Plex Sans',sans-serif;letter-spacing:-0.02em;color:var(--ui-text-highlighted)">Screens: article localization</h1>
|
||||
<p style="margin:0;max-width:880px;font-size:16px;line-height:26px;color:var(--ui-text-toned);text-wrap:pretty">One story, several language versions. Proposed feature: every API change is labelled as proposed. 1440 px at 50%, 390 px at 75%. The theme switch shows every state in dark mode. Imported localization reference. Product and API corrections are in docs/design/localization.md. Rendered parity and prototype flows remain unverified.</p>
|
||||
<p style="margin:0;max-width:880px;font-size:16px;line-height:26px;color:var(--ui-text-toned);text-wrap:pretty">One story, several language versions. Proposed feature: every API change is labelled as proposed. 1440 px at 50%, 1024 px at 50% for layout-dependent states, 390 px at 75%. The theme switch shows every state in dark mode. Each state shows its ID and fixture. Groups L6 and L9 are release 2. Repository code was unavailable, so existing behaviour comes from the design project only.</p>
|
||||
</div>
|
||||
<div role="radiogroup" aria-label="Theme" style="display:flex;border:1px solid var(--ui-border-accented);background:var(--ui-bg)">
|
||||
<sc-for list="{{ modes }}" as="m" hint-placeholder-count="3">
|
||||
@@ -33,15 +33,16 @@
|
||||
<section data-screen-label="{{ g.label }}" style="display:flex;flex-direction:column;gap:28px">
|
||||
<div style="display:flex;flex-direction:column;gap:6px;max-width:1100px">
|
||||
<h2 style="margin:0;font:600 28px/36px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ g.title }}</h2>
|
||||
<div style="display:flex;gap:16px;flex-wrap:wrap;font:400 14px/22px 'IBM Plex Mono',monospace;color:var(--ui-text-toned)"><span>{{ g.route }}</span><span style="color:var(--ui-text-muted)">{{ g.api }}</span></div>
|
||||
<div style="display:flex;gap:16px;flex-wrap:wrap;font:400 14px/22px 'IBM Plex Mono',monospace;color:var(--ui-text-toned)"><span>{{ g.route }}</span><span style="color:var(--ui-text-muted)">{{ g.api }}</span><span style="color:var(--ui-text-muted)">{{ g.count }}{{ g.rel }}</span></div>
|
||||
<sc-if value="{{ g.note }}"><p style="margin:4px 0 0;font-size:15px;line-height:24px;color:var(--ui-text-toned);text-wrap:pretty">{{ g.note }}</p></sc-if>
|
||||
</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:40px;align-items:flex-start">
|
||||
<sc-for list="{{ g.states }}" as="st" hint-placeholder-count="4">
|
||||
<div style="display:flex;flex-direction:column;gap:12px">
|
||||
<div style="display:flex;flex-direction:column;gap:2px;max-width:1040px"><span style="font:600 16px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ st.name }}</span><span style="font-size:13px;color:var(--ui-text-muted)">{{ st.trigger }}</span></div>
|
||||
<div style="display:flex;flex-direction:column;gap:2px;max-width:1040px"><span style="font:600 16px/24px 'IBM Plex Sans',sans-serif;color:var(--ui-text-highlighted)">{{ st.name }}</span><span style="font-size:13px;color:var(--ui-text-muted)">{{ st.trigger }}</span><span style="font:400 12px/18px 'IBM Plex Mono',monospace;color:var(--ui-text-muted)">{{ st.id }} · frame {{ st.sel }} · fixture {{ st.fx }}</span></div>
|
||||
<div style="display:flex;gap:24px;align-items:flex-start">
|
||||
<div style="width:{{ st.dw }};height:{{ st.dh }};overflow:hidden;border:1px solid var(--ui-border);flex-shrink:0"><div style="width:{{ st.fw }};height:{{ st.fh }};zoom:{{ st.z }}"><dc-import name="LocaleFrame" s="{{ st.s }}" mobile="{{ false }}" hint-size="1440px,900px"></dc-import></div></div>
|
||||
<sc-if value="{{ st.tri }}"><div style="width:512px;height:384px;overflow:hidden;border:1px solid var(--ui-border);flex-shrink:0"><div style="width:1024px;height:768px;zoom:0.5"><dc-import name="LocaleFrame" s="{{ st.s2 }}" mobile="{{ false }}" hint-size="1024px,768px"></dc-import></div></div></sc-if>
|
||||
<div style="width:293px;height:633px;overflow:hidden;border:1px solid var(--ui-border);flex-shrink:0"><div style="width:390px;height:844px;zoom:0.75"><dc-import name="LocaleFrame" s="{{ st.s }}" mobile="{{ true }}" hint-size="390px,844px"></dc-import></div></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,13 +77,28 @@ class Component extends DCLogic {
|
||||
if (p.theme !== this.props.theme && this.props.theme) this.setState({ mode: this.props.theme })
|
||||
this.apply()
|
||||
}
|
||||
groups() { return window.LOC.groups.map(g => ({ ...g, label: g.id + ' ' + g.title, states: g.states.map(a => ({ name: a[0], trigger: a[1], dw: a[4]==='narrow'?'717px':'720px', dh: a[4]==='narrow'?'538px':'450px', fw: a[4]==='narrow'?'1024px':'1440px', fh: a[4]==='narrow'?'768px':'900px', z: a[4]==='narrow'?0.7:0.5, s: { v: a[2], x: a[3] }, nar: a[4] === 'narrow' })) })) }
|
||||
groups() { return window.LOC.groups.map(g => ({ ...g, count: g.states.length + ' states · ', rel: g.release === 2 ? 'Release 2' : 'Release 1', label: g.id + ' ' + g.title, states: g.states.map(a => { const nar = a[4] === 'narrow', tri = a[4] === 'tri'; return { name: a[0], trigger: a[1], id: a[5], fx: a[6], sel: 'LocaleFrame{v:' + a[2] + ',x:' + (a[3] || "''") + '}', tri, dw: nar ? '717px' : '720px', dh: nar ? '538px' : '450px', fw: nar ? '1024px' : '1440px', fh: nar ? '768px' : '900px', z: nar ? 0.7 : 0.5, s: { v: a[2], x: a[3], narrow: nar }, s2: { v: a[2], x: a[3], narrow: true } } }) })) }
|
||||
ratio(a, b2) { const h = c => { c = c.replace('#', ''); return [0, 2, 4].map(i => parseInt(c.slice(i, i + 2), 16)) }, L = rgb => { const f = v => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4) }; return 0.2126 * f(rgb[0]) + 0.7152 * f(rgb[1]) + 0.0722 * f(rgb[2]) }, x = L(h(a)), y = L(h(b2)); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05) }
|
||||
mix(fg, bg, p) { const h = c => [1, 3, 5].map(i => parseInt(c.slice(i, i + 2), 16)), f = h(fg), g = h(bg); return '#' + f.map((v, i) => Math.round(v * p + g[i] * (1 - p)).toString(16).padStart(2, '0')).join('') }
|
||||
computed() {
|
||||
const FX = window.LOCFX, LOC = window.LOC, J = FX.SITES.journal
|
||||
const jv = FX.view({ site: J }), p2 = FX.view({ site: J, page: 2 }), fr = FX.view({ site: J, lang: 'fr' }), frm = FX.view({ site: J, lang: 'fr', tab: 'missing' }), rv = FX.view({ site: J, tab: 'review' }), au = FX.view({ site: J, author: 'Marta Alves' })
|
||||
const cnt = k => FX.count(k, J.stories), ver = J.stories.reduce((a, s) => a + Object.keys(s.v).length, 0), eq = (a, b2) => a === b2 ? 'Matches' : 'MISMATCH'
|
||||
const checks = [['Articles', 8, J.stories.length], ['Versions', 17, ver], ['Page size', 5, jv.size], ['Page 1 rows', 5, jv.rows.length], ['Page 2 rows', 3, p2.rows.length], ['Pages', 2, jv.pages], ['en versions / published', '7 / 6', cnt('en').join(' / ')], ['fr versions / published', '4 / 2', cnt('fr').join(' / ')], ['fr needing review', 2, fr.tabs[3][1]], ['de versions / published', '4 / 3', cnt('de').join(' / ')], ['ar versions', 1, cnt('ar')[0]], ['es versions', 1, cnt('es')[0]], ['it versions', 0, cnt('it')[0]], ['Articles without French', 4, frm.total], ['Needs translation tab', 7, jv.tabs[1][1]], ['Needs review tab', 2, rv.total], ['Author Marta Alves', 3, au.total], ['Setup articles (Journal + Notes + Archive)', 16, FX.SETUP.reduce((a, r) => a + r.n, 0)], ['Press: not in French', 0, FX.view({ site: FX.SITES.press, lang: 'fr', tab: 'missing' }).total]]
|
||||
const checkRows = checks.map(c => [c[0], String(c[1]), String(c[2]), eq(String(c[1]), String(c[2]))])
|
||||
const byFx = {}; LOC.groups.forEach(g => g.states.forEach(a => { (byFx[a[6]] = byFx[a[6]] || []).push(a[5]) }))
|
||||
const fxRows = Object.entries(byFx).map(([k, v]) => [k, FX.FIXTURE_NOTES[k.split(',')[0].replace(' (limited scope)', '').trim()] || '', v.length + ' states', v.length > 12 ? v.slice(0, 8).join(', ') + ' and ' + (v.length - 8) + ' more (every state not listed under another fixture)' : v.join(', ')])
|
||||
const cl = ['--ui-success', '--rc-success-soft'], ct = (m2) => { const t = m2 === 'dark' ? this.dark : this.light; const pr = [['Published badge', t['--ui-success'], t['--rc-success-soft'], 4.5], ['Info badge (Default, Tracked source)', t['--ui-info'], t['--rc-info-soft'], 4.5], ['Warning badge', t['--ui-warning'], t['--rc-warning-soft'], 4.5], ['Error badge', t['--ui-error'], t['--rc-error-soft'], 4.5], ['Neutral badge (Draft, Read only)', t['--ui-text-toned'], t['--ui-bg-elevated'], 4.5], ['Freshness badge (outlined)', t['--ui-warning'], t['--ui-bg'], 4.5], ['Selected row and menu item', t['--rc-on-sand'], t['--rc-sand'], 4.5], ['Coverage chip', t['--ui-text-highlighted'], t['--ui-bg'], 4.5], ['Dashed missing chip text', t['--ui-text-muted'], t['--ui-bg'], 4.5], ['Disabled-language chip (75%)', this.mix(t['--ui-text-highlighted'], t['--ui-bg'], .75), t['--ui-bg'], 4.5], ['Primary button', t['--ui-bg'], t['--ui-primary'], 4.5], ['Disabled menu item (60%, exempt)', this.mix(t['--ui-text-highlighted'], t['--ui-bg'], .6), t['--ui-bg'], 4.5], ['Disabled button (40%, exempt)', this.mix(t['--ui-text-highlighted'], t['--ui-bg'], .4), t['--ui-bg'], 4.5], ['Dashed and input borders (non-text)', t['--ui-border-accented'], t['--ui-bg'], 3]]; return pr.map(r => [r[0], this.ratio(r[1], r[2]).toFixed(2), r[3]]) }
|
||||
const L = ct('light'), D = ct('dark'), conRows = L.map((r, i) => [r[0], r[1], D[i][1], r[2] + ':1', (+r[1] >= r[2] && +D[i][1] >= r[2]) ? 'Pass' : (/exempt/.test(r[0]) ? 'Exempt (disabled)' : 'FAIL')])
|
||||
const cntRows = LOC.groups.map(g => [g.id + ' ' + g.title, String(g.states.length), String(g.states.filter(a => a[4] === 'tri').length), g.release === 2 ? 'Release 2' : 'Release 1']).concat([['Total', String(LOC.groups.reduce((a, g) => a + g.states.length, 0)), String(LOC.groups.reduce((a, g) => a + g.states.filter(a2 => a2[4] === 'tri').length, 0)), '']])
|
||||
return [{ title: 'Fixture index', cols: ['Fixture', 'Contents', 'Used by', 'State IDs'], w: '1.2fr 2fr .8fr 3fr', rows: fxRows }, { title: 'Fixture totals check (computed from locale-fixtures.js now)', cols: ['Figure', 'Decided', 'Computed', 'Result'], w: '2fr 1fr 1fr 1fr', rows: checkRows }, { title: 'State counts per group', cols: ['Group', 'States', 'Also at 1024', 'Release'], w: '3fr 1fr 1fr 1fr', rows: cntRows }, { title: 'Contrast of badges, chips and variants (computed from the tokens)', cols: ['Element', 'Light', 'Dark', 'Needs', 'Result'], w: '3fr 1fr 1fr 1fr 1.4fr', rows: conRows }]
|
||||
}
|
||||
renderVals() {
|
||||
const modes = [['system', 'System'], ['light', 'Light'], ['dark', 'Dark']].map(([k, label]) => {
|
||||
const on = this.state.mode === k
|
||||
return { label, on, bg: on ? 'var(--ui-primary)' : 'transparent', fg: on ? 'var(--ui-bg)' : 'var(--ui-text-toned)', pick: () => this.setState({ mode: k }) }
|
||||
})
|
||||
return { rootRef: this.rootRef, modes, groups: this.groups(), tables: window.LOC.tables }
|
||||
return { rootRef: this.rootRef, modes, groups: this.groups(), tables: window.LOC.tables.concat(this.computed()) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in new issue
Block a user