Compare commits

...

3 Commits

Author SHA1 Message Date
Maurice 3f3e2f0bcf chore(client): hold the refs count and the stylesheet size at what they are 2026-10-24 14:06:40 +02:00
Maurice f956ab07f3 fix(studio): tidy the head row, the layouts tab and the theme panel
Every piece of the head row is one raised tile of one height: the format
picker moved beside the tools, the assistant and help stand alone, the
close button is square like the rest, and the beta mark is gone. The
layouts tab lays the book's own and the built-in layouts out in sections
and in the same grid as the designed pages. In the theme panel the accent
dots sit in the sheet's corner, the three accents share one name, a font
keeps its name ahead of its kind, a doubled rule is gone, and the
inspector's empty hint is shorter.
2026-10-24 13:54:49 +02:00
Maurice 736594696e fix(tours): one summit per mountain in the places along a tour
The index holds a mountain as many pins named by whoever listed it, at its
foot as much as on top, so a hike up Mt. Takao listed it five times under
names like Mt.Takao, 東京都 高尾山 and 高尾山 (Mt. Takao). Rows that are one
place now merge as a group: like names in one spot for every kind, and for a
summit of the index like names anywhere on the mountain, linked through a
name that carries both scripts. The row kept is an OpenStreetMap object over
an index pin, then the plainest name. Two OpenStreetMap summits stay two.
2026-10-24 13:54:48 +02:00
69 changed files with 311 additions and 188 deletions
+1 -1
View File
@@ -26,5 +26,5 @@
"src/pages/atlas/useAtlas.ts": 1211,
"src/pages/tripPlanner/useTripPlanner.roadtrip.test.tsx": 3518,
"src/pages/tripPlanner/useTripPlanner.test.tsx": 3270,
"src/styles/studio.css": 1936
"src/styles/studio.css": 1918
}
@@ -215,13 +215,13 @@ export function StudioAssistantToggle({ assistant }: { assistant: StudioAssistan
return (
<button
type="button"
className={`st-tool st-assistant-toggle ${assistant.open ? 'is-on' : ''}`}
className={`st-tool is-icon st-head-solo st-assistant-toggle ${assistant.open ? 'is-on' : ''}`}
onClick={assistant.toggle}
aria-pressed={assistant.open}
aria-label={t('journey.studio.assistant.toggle')}
title={t('journey.studio.assistant.toggleHint')}
>
<MessageSquare size={14} />
<span className="st-tool-label">{t('journey.studio.assistant.toggle')}</span>
<MessageSquare size={15} />
</button>
);
}
@@ -53,7 +53,7 @@ describe('StudioHeader', () => {
it("FE-STUDIO-HEAD-001: names the journey under the Studio's mark, with its cover as the tile", () => {
head();
expect(screen.getByRole('heading', { name: 'Japan 2026' })).toBeInTheDocument();
expect(screen.getByText('journey.studio.beta')).toBeInTheDocument();
expect(screen.getByRole('img', { name: 'TREK Studio' })).toBeInTheDocument();
expect(document.querySelector('.st-head-tile img')?.getAttribute('src')).toBe('/uploads/cover.jpg');
});
+21 -18
View File
@@ -15,14 +15,15 @@ import type { StudioAssistant } from './useStudioAssistant';
/**
* The Studio's head band, in the shape every TREK dialog has: a raised tile on
* the left, a small line above the title, the title, a line under it, and the
* round close button at the far end.
* the left, the Studio's mark above the title, the title, a line under it, and
* the close button at the far end.
*
* The tile is the journey's cover and the title its name, so it is always clear
* whose book this is. What the book is printed as and whether it is saved sit
* under the title, because they describe it. The tools on the right are grouped
* by what they do: who else is here, editing (undo, redo, paste), making
* (auto layout, the assistant), and getting the book out (help, export). How
* whose book this is. Whether it is saved sits under the title, because it
* describes it. The tools on the right are grouped by what they do: who else
* is here, what the book is printed as, editing (undo, redo, paste), auto
* layout, the assistant, help, and getting the book out. Every piece of
* the row is one raised tile of one height, export alone in the accent. How
* the page is looked at, the book view, lives with the zoom under the page.
*/
export function StudioHeader({
@@ -41,11 +42,9 @@ export function StudioHeader({
<div className="st-head-text">
<div className="st-head-eyebrow">
<StudioWordmark style={{ height: 17 }} />
<span className="st-beta">{s.t('journey.studio.beta')}</span>
</div>
<h1 className="st-head-title">{s.journey?.title || s.t('journey.title')}</h1>
<div className="st-head-sub">
<FormatPicker s={s} />
<SaveIndicator
state={s.saveState}
t={s.t}
@@ -61,6 +60,10 @@ export function StudioHeader({
<div className="st-head-tools">
<PeerBadges peers={s.peers} t={s.t} />
<div className="st-head-group">
<FormatPicker s={s} />
</div>
<div className="st-head-group">
<IconTool label={s.t('journey.studio.undo')} disabled={!s.canUndo} onClick={s.undo}>
<Undo2 size={15} />
@@ -73,15 +76,15 @@ export function StudioHeader({
<div className="st-head-group">
<AutoLayoutButton s={s} />
{/* The book's assistant; only where it can run for this user. */}
<StudioAssistantToggle assistant={assistant} />
</div>
{/* The book's assistant, a tile of its own like help; only where it can run for this user. */}
<StudioAssistantToggle assistant={assistant} />
{/* Studio has no navbar, so the help center's button rides on its own bar. */}
<HelpButton className="st-tool is-icon" />
<HelpButton className="st-tool is-icon st-head-solo" />
<button
type="button"
className="st-tool is-primary"
className="st-tool is-primary st-head-solo"
onClick={onExport}
disabled={!s.doc}
title={s.t('journey.studio.export')}
@@ -242,8 +245,8 @@ function AutoLayoutButton({ s }: { s: Studio }) {
}
/**
* The format, as a chip under the book's name: it says what the book is
* printed as, and opening it changes that.
* The format, as a tool of the head row: it says what the book is printed as,
* and opening it changes that.
*
* A native <select> was the first draft and it was wrong: the browser draws the
* list in its own chrome, so it ignores the theme entirely and drops a list that
@@ -264,7 +267,7 @@ function FormatPicker({ s }: { s: Studio }) {
<div className="st-picker" ref={box}>
<button
type="button"
className="st-head-chip"
className="st-tool"
onClick={() => setOpen((o) => !o)}
aria-haspopup="listbox"
aria-expanded={open}
@@ -278,16 +281,16 @@ function FormatPicker({ s }: { s: Studio }) {
: { height: 12, width: Math.max(6, Math.round((12 * width) / height)) }
}
/>
<span>
<span className="st-tool-label">
{s.preset === 'custom'
? `${Math.round(s.page.pageWidth)} × ${Math.round(s.page.pageHeight)} mm`
: s.t(active.labelKey)}
</span>
<ChevronDown size={12} style={{ opacity: 0.55 }} />
<ChevronDown size={13} style={{ opacity: 0.5 }} />
</button>
{open && (
<div className="st-menu st-head-menu" role="listbox">
<div className="st-menu" role="listbox">
{PAGE_PRESET_ORDER.map((id) => {
const p = PAGE_PRESETS[id];
const on = id === s.preset;
@@ -35,10 +35,10 @@ export function StudioSavedLayouts({
const scale = width / (wMm * pxPerMm)
return (
<div className="st-saved-layouts">
<div className="st-section st-saved-layouts">
<div className="st-section-label">{t('journey.studio.myLayouts')}</div>
{fitting.length === 0 && <p className="st-hint">{t('journey.studio.myLayoutsEmpty')}</p>}
<div className="st-thumbs">
<div className={`st-thumbs st-tpl-grid ${single ? 'is-pages' : 'is-spreads'}`}>
{fitting.map(layout => (
<div className="st-thumb-row" key={layout.id}>
<button type="button" className="st-thumb" onClick={() => applyLayout(active, layout.id)}>
@@ -79,7 +79,6 @@ export function StudioSavedLayouts({
{t('journey.studio.saveLayout')}
</button>
</Tooltip>
<div className="st-section-label st-builtin-label">{t('journey.studio.builtInLayouts')}</div>
</div>
)
}
@@ -117,7 +117,7 @@ function BuiltInLayouts({
const scale = width / (page.pageWidth * (single ? 1 : 2) * pxPerMm);
const templates = single ? COVER_TEMPLATES : TEMPLATES;
return (
<div className="st-thumbs">
<div className={`st-thumbs st-tpl-grid ${single ? 'is-pages' : 'is-spreads'}`}>
{templates.map((tpl) => (
<button type="button" key={tpl.id} className="st-thumb" onClick={() => onPick(tpl)}>
{/* A page is laid out from its top left in either reading direction. */}
@@ -183,7 +183,7 @@ export function StudioTemplatesPanel({
if (!doc || !spread || !ctx) return null;
const single = spread.role !== 'inner';
const templates = templatesForSpread(spread, filter);
const { full, card: cardWidth } = previewWidths(list.width, single);
const { card: cardWidth } = previewWidths(list.width, single);
/**
* One undo step: the page laid out, nothing selected, and the photos at hand
@@ -247,8 +247,11 @@ export function StudioTemplatesPanel({
</div>
<p className="st-hint">{t('studio.templates.hint')}</p>
</div>
<StudioSavedLayouts page={page} width={full} pxPerMm={pxPerMm} t={t} />
<BuiltInLayouts spread={spread} page={page} pxPerMm={pxPerMm} width={full} t={t} onPick={pickBuiltIn} />
<StudioSavedLayouts page={page} width={cardWidth} pxPerMm={pxPerMm} t={t} />
<div className="st-section">
<div className="st-section-label">{t('journey.studio.builtInLayouts')}</div>
<BuiltInLayouts spread={spread} page={page} pxPerMm={pxPerMm} width={cardWidth} t={t} onPick={pickBuiltIn} />
</div>
</div>
</>
);
@@ -67,7 +67,11 @@ function ThemeCard({
);
}
/** One palette entry: its colour, which is the picker, and its name. */
/**
* One palette entry: its colour, which is the picker, and its name. The three
* accents share one name under them, so each shows its colour alone and says
* its own name to a screen reader and on hover.
*/
function PaletteEntry({
name,
hex,
@@ -79,13 +83,14 @@ function PaletteEntry({
onPick: (hex: string) => void;
t: StudioT;
}) {
const accent = name.startsWith('accent');
const label = t(`studio.theme.token.${name}`);
return (
<label className="st-theme-colour">
<label className="st-theme-colour" title={label}>
<span className="st-theme-chip" style={{ background: hex }}>
<input type="color" value={hex} aria-label={label} onChange={(e) => onPick(e.target.value)} />
</span>
<span className="st-theme-colour-name">{label}</span>
{!accent && <span className="st-theme-colour-name">{label}</span>}
</label>
);
}
@@ -181,6 +186,7 @@ export function StudioThemePanel({ t }: { t: StudioT }) {
t={t}
/>
))}
<span className="st-theme-colour-name st-theme-accents-name">{t('studio.theme.accents')}</span>
</div>
<button
type="button"
+4 -10
View File
@@ -2,16 +2,10 @@
Studio in the planner's window (assistantFrame.tsx), so the page and the
inspector keep all their room. */
/* The toggle is the bar's newest tool, so it gives up its label first: from 1360
to 1519 it is an icon while the others keep theirs, and the head band fits
where it did before. */
@media (max-width: 1519px) {
.trek-dash .st-assistant-toggle .st-tool-label {
display: none;
}
.trek-dash .st-assistant-toggle {
padding: 0 8px;
}
/* The toggle is an icon tile of the head row; open, it reads as pressed. */
.trek-dash .st-tool.st-head-solo.st-assistant-toggle.is-on {
background: color-mix(in oklch, var(--accent) 14%, var(--surface));
color: var(--ink);
}
.trek-dash .st-assistant-notice {
+13
View File
@@ -71,6 +71,19 @@
white-space: nowrap;
}
/* In the closed picker the face's name comes first: the kind gives way, cut
short and then gone, before a letter of the name is lost. */
.trek-dash .st-fontpick-current .st-fontpick-face {
flex: 0 1 auto;
}
.trek-dash .st-fontpick-current .st-fontpick-span {
flex: 1 1 0;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
text-align: end;
}
.trek-dash .st-fontpick-caret {
flex: none;
color: var(--ink-3);
+64 -51
View File
@@ -1,6 +1,6 @@
/* ----------------- head band (StudioHeader.tsx) ----------------- *
* The shape every TREK dialog head has: a tinted band, a raised tile, a small
* line above the title and a round close button at the end. The band keeps the
* line above the title and the close button at the end. The band keeps the
* glass of the old bar under its tint, so the journey still shows through.
*/
.trek-dash .st-head {
@@ -49,13 +49,13 @@
flex: 1 1 auto;
}
/* The wordmark and the beta mark: what this screen is, quietly, above its name. */
/* The wordmark: what this screen is, above its name, in the title's own ink. */
.trek-dash .st-head-eyebrow {
display: flex;
align-items: center;
gap: 7px;
height: 17px;
color: var(--ink-2);
color: var(--ink);
}
.trek-dash .st-head-eyebrow svg {
flex: none;
@@ -80,48 +80,6 @@
min-width: 0;
}
/* The format, as a chip that opens the format menu. */
.trek-dash .st-head-chip {
display: inline-flex;
align-items: center;
gap: 6px;
height: 22px;
padding-inline: 7px 8px;
border-radius: 999px;
background: var(--surface);
box-shadow: var(--sh-xs);
color: var(--ink-2);
font-size: 11.5px;
font-weight: 500;
white-space: nowrap;
transition:
color 0.16s,
box-shadow 0.16s;
}
.trek-dash .st-head-chip:hover {
color: var(--ink);
box-shadow: var(--sh-sm);
}
.trek-dash .st-head-chip:focus {
outline: none;
}
.trek-dash .st-head-chip:focus-visible {
box-shadow:
0 0 0 2px var(--accent-soft),
0 0 0 3px var(--accent);
}
.trek-dash .st-head-chip-shape {
flex: none;
border: 1.5px solid currentColor;
border-radius: 2px;
opacity: 0.7;
}
/* The chip sits on the left, so its menu opens towards the page, not off the window. */
.trek-dash .st-head-menu {
inset-inline-start: 0;
inset-inline-end: auto;
}
.trek-dash .st-head-tools {
display: flex;
align-items: center;
@@ -130,32 +88,79 @@
margin-inline-start: auto;
}
/* Every piece of the row is one raised tile of one height and one corner:
the groups, who is here, help, export and close. */
.trek-dash .st-head {
--st-head-h: 36px;
}
/* Tools that belong together share one raised group, like a segmented control. */
.trek-dash .st-head-group {
display: flex;
align-items: center;
gap: 2px;
height: var(--st-head-h);
padding: 3px;
border-radius: 12px;
background: var(--surface);
box-shadow: var(--sh-xs);
}
/* A tool standing alone is a group of one: help, and export in the accent. */
.trek-dash .st-tool.st-head-solo {
height: var(--st-head-h);
border-radius: 12px;
}
.trek-dash .st-tool.is-icon.st-head-solo {
width: var(--st-head-h);
background: var(--surface);
box-shadow: var(--sh-xs);
}
.trek-dash .st-tool.is-icon.st-head-solo:hover:not(:disabled) {
background: var(--surface);
box-shadow: var(--sh-sm);
}
.trek-dash .st-tool.is-primary.st-head-solo {
padding-inline: 16px;
box-shadow: var(--sh-xs);
}
/* Who is here, as a tile like the tools beside it. */
.trek-dash .st-head .st-peers {
height: var(--st-head-h);
padding-inline: 8px 12px;
margin-inline-start: 0;
border: 0;
border-radius: 12px;
background: var(--surface);
box-shadow: var(--sh-xs);
}
.trek-dash .st-head .st-peer-dot {
width: 24px;
height: 24px;
border-color: var(--surface);
}
.trek-dash .st-head .st-peer-label {
font-size: 12.5px;
}
.trek-dash .st-head-close {
display: grid;
place-items: center;
width: 36px;
height: 36px;
width: var(--st-head-h);
height: var(--st-head-h);
flex: none;
margin-inline-start: 2px;
border-radius: 999px;
border-radius: 12px;
background: var(--surface);
box-shadow: var(--sh-sm);
box-shadow: var(--sh-xs);
color: var(--ink-3);
transition: color 0.16s;
transition:
color 0.16s,
box-shadow 0.16s;
}
.trek-dash .st-head-close:hover {
color: var(--ink);
box-shadow: var(--sh-sm);
}
.trek-dash .st-head-close:focus {
outline: none;
@@ -166,6 +171,14 @@
0 0 0 3px var(--accent);
}
/* The format's shape, drawn in the tool's own ink. */
.trek-dash .st-head-chip-shape {
flex: none;
border: 1.5px solid currentColor;
border-radius: 2px;
opacity: 0.7;
}
/* The book view moved to the zoom, where the page's view lives; on reads as pressed. */
.trek-dash .st-zoom button.is-on {
background: color-mix(in oklch, var(--accent) 14%, transparent);
+4
View File
@@ -28,6 +28,10 @@
.trek-dash .st-tpl-grid.is-spreads {
grid-template-columns: minmax(0, 1fr);
}
/* The book's own layouts and the built-in ones lie in the same grid as the designed pages. */
.trek-dash .st-thumbs.st-tpl-grid {
display: grid;
}
.trek-dash .st-tpl-card {
min-width: 0;
+34 -6
View File
@@ -65,10 +65,13 @@
line-height: 1;
letter-spacing: -0.01em;
}
/* The accents sit in the sheet's top corner, clear of a wide "Aa" below. */
.trek-dash .st-theme-dots {
position: absolute;
top: 7px;
inset-inline-end: 8px;
display: flex;
gap: 3px;
padding-bottom: 2px;
}
.trek-dash .st-theme-dots > span {
width: 11px;
@@ -127,6 +130,28 @@
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 6px;
}
/* The accents' one name spans their three columns, level with the others'
names; every entry is placed, so the name does not push them a row down. */
.trek-dash .st-theme-palette > .st-theme-colour {
grid-row: 1;
}
.trek-dash .st-theme-palette > :nth-child(2) {
grid-column: 2;
}
.trek-dash .st-theme-palette > :nth-child(3) {
grid-column: 3;
}
.trek-dash .st-theme-palette > :nth-child(4) {
grid-column: 4;
}
.trek-dash .st-theme-palette > :nth-child(5) {
grid-column: 5;
}
.trek-dash .st-theme-accents-name {
grid-row: 1;
grid-column: 3 / span 3;
align-self: end;
}
.trek-dash .st-theme-colour {
display: flex;
flex-direction: column;
@@ -154,10 +179,14 @@
opacity: 0;
cursor: pointer;
}
/* On one line: broken inside a word, "Accent" read as "Accen t". A name the
column cannot hold is cut, and the entry's title says it whole. */
.trek-dash .st-theme-colour-name {
max-width: 100%;
overflow-wrap: anywhere;
hyphens: auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: -0.01em;
text-align: center;
line-height: 1.2;
font-size: calc(10.5px * var(--fs-scale-caption, 1));
@@ -210,13 +239,12 @@
padding: 0 2px;
}
/* The last section's own rule already sets this apart; a second one doubled it. */
.trek-dash .st-theme-apply {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 6px;
padding-top: 12px;
border-top: 1px solid var(--line-2);
padding-top: 14px;
}
.trek-dash .st-theme-apply .st-hint {
padding: 0 2px;
+2 -20
View File
@@ -1447,24 +1447,6 @@
color: var(--ink);
}
/* Small, quiet and set apart — a beta mark states a fact about the feature; it
is not a call to action and should not read as one. */
.trek-dash .st-beta {
display: inline-flex;
align-items: center;
height: 16px;
padding: 0 6px;
border-radius: 999px;
font-size: 9.5px;
font-weight: 700;
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--accent);
background: color-mix(in oklch, var(--accent) 13%, transparent);
border: 1px solid color-mix(in oklch, var(--accent) 26%, transparent);
flex: none;
}
/* ----------------- custom trim ----------------- */
.trek-dash .st-menu-sep {
height: 1px;
@@ -1918,9 +1900,9 @@
* The book's own layouts sit above the built-in ones, with the save button
* closing their group, so what a click keeps and where it goes read as one.
*/
.trek-dash .st-saved-layouts { display: flex; flex-direction: column; margin-bottom: 14px; }
.trek-dash .st-saved-layouts { display: flex; flex-direction: column; }
.trek-dash .st-saved-layouts .st-hint { margin: 0 0 4px; }
.trek-dash .st-saved-layouts .st-thumbs:empty { display: none; }
.trek-dash .st-saved-layouts .st-add-page { margin-top: 8px; }
.trek-dash .st-saved-layouts .st-add-page:disabled { opacity: .5; cursor: default; }
.trek-dash .st-saved-layouts .st-add-page:disabled:hover { background: none; border-color: var(--line-2); color: var(--ink-3); }
.trek-dash .st-saved-layouts .st-builtin-label { margin-top: 18px; }
@@ -66,10 +66,6 @@ function metresApart(a: Pick<TourPoi, 'lat' | 'lng'>, b: { lat: number; lng: num
return Math.hypot(dLat, dLng);
}
function withinSamePlace(a: Pick<TourPoi, 'lat' | 'lng'>, b: { lat: number; lng: number }): boolean {
return metresApart(a, b) <= SAME_PLACE_M;
}
/** A name as compared: without case, accents or punctuation. */
function nameKey(name: string): string {
return name
@@ -202,21 +198,82 @@ function tourCategoryOf(mapsCategory: string, asked: readonly TourPoiCategory[])
}
/**
* The places found, each once and within the corridor, with where they lie.
* OpenStreetMap often maps one place twice, as a point and as its outline, and
* the index may hold it beside OpenStreetMap: one name in one spot is one
* place. An unnamed place (a tap, a bench) is never merged with another.
* Within this many metres, a summit of the index and another of a like name
* are one mountain. The index holds a mountain as many pins, placed by whoever
* listed it, at its foot as much as on top, so they lie a mountain's breadth
* apart. Two OpenStreetMap summits are two peaks, as the Watzmann and the
* Kleiner Watzmann are.
*/
const SAME_MOUNTAIN_M = 3000;
/**
* Scripts written without spaces between words: there one name inside another
* is a word of it, as 高尾山 is of 八王子高尾山, where in Latin "Dom" is not
* "Domplatz".
*/
const UNSPACED_SCRIPT = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Thai}]{2,}$/u;
/** {@link similarNames}, or one word written without spaces inside the other name. */
function likeNames(a: string, b: string): boolean {
if (similarNames(a, b)) return true;
const inside = (word: string, name: string) => UNSPACED_SCRIPT.test(word) && name.replaceAll(' ', '').includes(word);
return a.split(' ').some((word) => inside(word, b)) || b.split(' ').some((word) => inside(word, a));
}
/**
* Two rows that are one place: the same name in one spot, as OpenStreetMap
* maps a place twice, as a point and as its outline, or the index holds it
* beside OpenStreetMap; a like name in one spot; or, for a summit of the
* index, a like name anywhere on the mountain. An unnamed place (a tap, a
* bench) is never one with another.
*/
function samePlace(a: TourPoi, b: TourPoi): boolean {
const name = nameKey(a.name);
const other = nameKey(b.name);
if (!name || !other || a.category !== b.category) return false;
const apart = metresApart(a, b);
const pinned = a.source !== 'openstreetmap' || b.source !== 'openstreetmap';
if (a.category === 'peak' && pinned && apart <= SAME_MOUNTAIN_M) return likeNames(name, other);
return apart <= SAME_PLACE_M && (name === other || likeNames(name, other));
}
/**
* The row a place mapped several times keeps: an OpenStreetMap object over an
* index pin, then the plainest name ("高尾山" rather than "Top Of Mt.Takao"),
* then the first found.
*/
function keptOf(rows: readonly TourPoi[]): TourPoi {
const plainness = (row: TourPoi) => [row.source === 'openstreetmap' ? 0 : 1, nameKey(row.name).length];
return rows.reduce((best, row) => {
const [a, b] = [plainness(row), plainness(best)];
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1]) ? row : best;
});
}
/**
* The rows that are one place, each once: a row joins every group it is one
* place with, so a bridge like "高尾山 (Mt. Takao)" joins "Mt.Takao" and
* "高尾山" although those two share no word.
*/
function oncePerPlace(rows: readonly TourPoi[]): TourPoi[] {
const groups: TourPoi[][] = [];
for (const row of rows) {
const joined = groups.filter((group) => group.some((other) => samePlace(row, other)));
const merged = [...joined.flat(), row];
groups.splice(0, groups.length, ...groups.filter((group) => !joined.includes(group)), merged);
}
const kept = new Set(groups.map(keptOf));
// In the order found, so the first of equals stays first.
return rows.filter((row) => kept.has(row));
}
/** The places found, each once and within the corridor, with where they lie. */
function placesWithin(found: MapsPoi[], body: TourPoiSearchRequest, line: Array<[number, number]>): TourPoi[] {
const kept = new Map<string, TourPoi>();
const byName = new Map<string, TourPoi[]>();
for (const poi of found) {
const category = tourCategoryOf(poi.category, body.categories);
const near = nearnessToLine({ lat: poi.lat, lng: poi.lng }, line);
if (!category || !near || near.distanceM > body.corridor_m || kept.has(poi.osm_id)) continue;
const name = poi.name.trim().toLocaleLowerCase();
const namesakes = name ? (byName.get(name) ?? []) : [];
if (namesakes.some((other) => withinSamePlace(other, poi))) continue;
const place: TourPoi = {
id: poi.osm_id,
name: poi.name,
@@ -234,9 +291,8 @@ function placesWithin(found: MapsPoi[], body: TourPoiSearchRequest, line: Array<
...bikeChargingOf(poi),
};
kept.set(poi.osm_id, place);
if (name) byName.set(name, [...namesakes, place]);
}
return [...kept.values()];
return oncePerPlace([...kept.values()]);
}
/**
@@ -139,6 +139,28 @@ describe('TourPoiSearchService', () => {
expect(result.pois.map((found) => found.id)).toEqual(['node:9', 'node:10']);
});
it('TOUR-POI-019: the pins of one mountain under several names are one summit, two mapped summits stay two', async () => {
const pin = (id: string, name: string, lat: number) =>
poi(id, lat, 11.1003, 'peak', { name, source: 'trek-places', poi_type: 'mountain' });
poisAlong.mockResolvedValue(
answer([
// Pins at the foot of the mountain and on top, named in two scripts and bridged by one that has both.
pin('gers:1', 'Mt.Takao', 47.401),
pin('gers:2', '東京都 高尾山', 47.4011),
pin('gers:3', '高尾山 (Mt. Takao)', 47.403),
pin('gers:4', '八王子高尾山', 47.405),
pin('gers:5', '高尾山', 47.419),
pin('gers:6', 'Top Of Mt.Takao', 47.4191),
// Another mountain on the way, and two summits of OpenStreetMap a kilometre apart.
pin('gers:7', 'Mt.Kagenobu', 47.41),
poi('node:1', 47.406, 11.1003, 'peak', { name: 'Watzmann' }),
poi('node:2', 47.415, 11.1003, 'peak', { name: 'Kleiner Watzmann' }),
]),
);
const result = await service.search({ tour_type: 'hike', categories: ['peak'], corridor_m: 500, line: LINE });
expect(result.pois.map((found) => found.name)).toEqual(['Watzmann', 'Mt.Kagenobu', 'Kleiner Watzmann', '高尾山']);
});
it('TOUR-POI-002: asks the map search along the line for what each category means, and names a hit by the category asked', async () => {
poisAlong.mockResolvedValue(answer([poi('node:cafe', 47.41, 11.1, 'cafe')]));
const result = await service.search({
+1 -2
View File
@@ -317,7 +317,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'مربع 30 × 30 سم',
'journey.studio.pages': 'الصفحات',
'journey.studio.cover': 'الغلاف',
'journey.studio.inspectorEmpty': 'حدّد عنصرًا في الصفحة لتعديله.',
'journey.studio.inspectorEmpty': 'حدّد عنصرًا لتعديله',
'journey.studio.autoLayout': 'تخطيط تلقائي',
'journey.studio.export': 'تصدير',
'journey.studio.day': 'اليوم {day}',
@@ -572,7 +572,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'حذف الصفحة',
'journey.studio.movePageUp': 'نقل للأمام',
'journey.studio.movePageDown': 'نقل للخلف',
'journey.studio.beta': 'تجريبي',
'journey.studio.addProsCons': 'الإيجابيات والسلبيات',
'journey.studio.showMarks': 'علامات',
'journey.studio.formatCustom': 'مقاس مخصص',
+1
View File
@@ -37,6 +37,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'لون مميز',
'studio.theme.token.accent2': 'لون مميز 2',
'studio.theme.token.accent3': 'لون مميز 3',
'studio.theme.accents': 'ألوان مميزة',
'studio.theme.fromPhotos': 'ألوان من الصور',
'studio.theme.noPhotoColours': 'لا تحتوي الصور على لون يمكن أخذه',
'studio.theme.fonts': 'الخطوط',
+1 -2
View File
@@ -319,7 +319,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Kvadrat 30 × 30 sm',
'journey.studio.pages': 'Səhifələr',
'journey.studio.cover': 'Üzlük',
'journey.studio.inspectorEmpty': 'Redaktə etmək üçün səhifədə bir element seçin.',
'journey.studio.inspectorEmpty': 'Redaktə üçün nəsə seçin',
'journey.studio.autoLayout': 'Avtomatik tərtibat',
'journey.studio.export': 'İxrac et',
'journey.studio.day': 'GÜN {day}',
@@ -596,7 +596,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Açılımı sil',
'journey.studio.movePageUp': 'Əvvələ daşı',
'journey.studio.movePageDown': 'Sonraya daşı',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Müsbət və mənfi cəhətlər',
'journey.studio.showMarks': 'Nişanlar',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Vurğu',
'studio.theme.token.accent2': 'Vurğu 2',
'studio.theme.token.accent3': 'Vurğu 3',
'studio.theme.accents': 'Vurğular',
'studio.theme.fromPhotos': 'Fotolardan rənglər',
'studio.theme.noPhotoColours': 'Fotolarda götürüləcək rəng yoxdur',
'studio.theme.fonts': 'Şriftlər',
+1 -2
View File
@@ -296,7 +296,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Quadrado 30 × 30 cm',
'journey.studio.pages': 'Páginas',
'journey.studio.cover': 'Capa',
'journey.studio.inspectorEmpty': 'Selecione algo na página para editar.',
'journey.studio.inspectorEmpty': 'Selecione algo para editar',
'journey.studio.autoLayout': 'Layout automático',
'journey.studio.export': 'Exportar',
'journey.studio.day': 'DIA {day}',
@@ -547,7 +547,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Excluir página',
'journey.studio.movePageUp': 'Mover para antes',
'journey.studio.movePageDown': 'Mover para depois',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Prós e contras',
'journey.studio.showMarks': 'Marcas',
'journey.studio.formatCustom': 'Tamanho próprio',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Destaque',
'studio.theme.token.accent2': 'Destaque 2',
'studio.theme.token.accent3': 'Destaque 3',
'studio.theme.accents': 'Destaques',
'studio.theme.fromPhotos': 'Cores das fotos',
'studio.theme.noPhotoColours': 'As fotos não têm cor para aproveitar',
'studio.theme.fonts': 'Fontes',
+1 -2
View File
@@ -294,7 +294,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Quadrat 30 × 30 cm',
'journey.studio.pages': 'Pàgines',
'journey.studio.cover': 'Coberta',
'journey.studio.inspectorEmpty': 'Selecciona alguna cosa a la pàgina per editar-la.',
'journey.studio.inspectorEmpty': 'Selecciona alguna cosa per editar',
'journey.studio.autoLayout': 'Disseny automàtic',
'journey.studio.export': 'Exporta',
'journey.studio.day': 'DIA {day}',
@@ -546,7 +546,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Elimina la pàgina',
'journey.studio.movePageUp': 'Mou abans',
'journey.studio.movePageDown': 'Mou després',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Pros i contres',
'journey.studio.showMarks': 'Marques',
'journey.studio.formatCustom': 'Mida pròpia',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Accent', // same-as-en
'studio.theme.token.accent2': 'Accent 2', // same-as-en
'studio.theme.token.accent3': 'Accent 3', // same-as-en
'studio.theme.accents': 'Accents', // same-as-en
'studio.theme.fromPhotos': 'Colors de les fotos',
'studio.theme.noPhotoColours': 'Les fotos no tenen cap color per agafar',
'studio.theme.fonts': 'Tipografies',
+1 -2
View File
@@ -300,7 +300,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Čtverec 30 × 30 cm',
'journey.studio.pages': 'Stránky',
'journey.studio.cover': 'Obálka',
'journey.studio.inspectorEmpty': 'Vyberte něco na stránce a můžete to upravit.',
'journey.studio.inspectorEmpty': 'Vyberte něco k úpravě',
'journey.studio.autoLayout': 'Automatické rozvržení',
'journey.studio.export': 'Exportovat',
'journey.studio.day': 'DEN {day}',
@@ -551,7 +551,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Smazat stranu',
'journey.studio.movePageUp': 'Posunout dopředu',
'journey.studio.movePageDown': 'Posunout dozadu',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Klady a zápory',
'journey.studio.showMarks': 'Značky',
'journey.studio.formatCustom': 'Vlastní formát',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Akcent',
'studio.theme.token.accent2': 'Akcent 2',
'studio.theme.token.accent3': 'Akcent 3',
'studio.theme.accents': 'Akcenty',
'studio.theme.fromPhotos': 'Barvy z fotek',
'studio.theme.noPhotoColours': 'Fotky nemají žádnou barvu k převzetí',
'studio.theme.fonts': 'Písma',
+1 -2
View File
@@ -296,7 +296,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Quadratisch 30 × 30 cm',
'journey.studio.pages': 'Seiten',
'journey.studio.cover': 'Cover', // same-as-en
'journey.studio.inspectorEmpty': 'Wähle etwas auf der Seite aus, um es zu bearbeiten.',
'journey.studio.inspectorEmpty': 'Wähle etwas zum Bearbeiten',
'journey.studio.autoLayout': 'Automatisch anordnen',
'journey.studio.export': 'Exportieren',
'journey.studio.day': 'TAG {day}',
@@ -548,7 +548,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Seite löschen',
'journey.studio.movePageUp': 'Nach vorne',
'journey.studio.movePageDown': 'Nach hinten',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Pro & Contra',
'journey.studio.showMarks': 'Zeichen',
'journey.studio.formatCustom': 'Eigenes Format',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Akzent',
'studio.theme.token.accent2': 'Akzent 2',
'studio.theme.token.accent3': 'Akzent 3',
'studio.theme.accents': 'Akzente',
'studio.theme.fromPhotos': 'Farben aus den Fotos',
'studio.theme.noPhotoColours': 'Die Fotos haben keine Farbe zum Übernehmen',
'studio.theme.fonts': 'Schriften',
+1 -2
View File
@@ -293,7 +293,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Square 30 × 30 cm',
'journey.studio.pages': 'Pages',
'journey.studio.cover': 'Cover',
'journey.studio.inspectorEmpty': 'Select something on the page to edit it.',
'journey.studio.inspectorEmpty': 'Select something to edit',
'journey.studio.autoLayout': 'Auto layout',
'journey.studio.export': 'Export',
'journey.studio.day': 'DAY {day}',
@@ -543,7 +543,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Delete spread',
'journey.studio.movePageUp': 'Move earlier',
'journey.studio.movePageDown': 'Move later',
'journey.studio.beta': 'Beta',
'journey.studio.addProsCons': 'Pros & Cons',
'journey.studio.showMarks': 'Marks',
'journey.studio.formatCustom': 'Custom size',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Accent',
'studio.theme.token.accent2': 'Accent 2',
'studio.theme.token.accent3': 'Accent 3',
'studio.theme.accents': 'Accents',
'studio.theme.fromPhotos': 'Colours from the photos',
'studio.theme.noPhotoColours': 'The photos have no colour to take',
'studio.theme.fonts': 'Typefaces',
+1 -2
View File
@@ -295,7 +295,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Cuadrado 30 × 30 cm',
'journey.studio.pages': 'Páginas',
'journey.studio.cover': 'Portada',
'journey.studio.inspectorEmpty': 'Selecciona algo en la página para editarlo.',
'journey.studio.inspectorEmpty': 'Selecciona algo para editar',
'journey.studio.autoLayout': 'Diseño automático',
'journey.studio.export': 'Exportar',
'journey.studio.day': 'DÍA {day}',
@@ -546,7 +546,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Eliminar página',
'journey.studio.movePageUp': 'Mover antes',
'journey.studio.movePageDown': 'Mover después',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Pros y contras',
'journey.studio.showMarks': 'Marcas',
'journey.studio.formatCustom': 'Tamaño propio',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Acento',
'studio.theme.token.accent2': 'Acento 2',
'studio.theme.token.accent3': 'Acento 3',
'studio.theme.accents': 'Acentos',
'studio.theme.fromPhotos': 'Colores de las fotos',
'studio.theme.noPhotoColours': 'Las fotos no tienen ningún color que tomar',
'studio.theme.fonts': 'Tipografías',
+1 -2
View File
@@ -295,7 +295,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Ruut 30 × 30 cm',
'journey.studio.pages': 'Lehed',
'journey.studio.cover': 'Kaas',
'journey.studio.inspectorEmpty': 'Muutmiseks vali lehel mõni element.',
'journey.studio.inspectorEmpty': 'Vali midagi muutmiseks',
'journey.studio.autoLayout': 'Automaatne paigutus',
'journey.studio.export': 'Eksport',
'journey.studio.day': '{day}. PÄEV',
@@ -544,7 +544,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Kustuta lehepaar',
'journey.studio.movePageUp': 'Liiguta ettepoole',
'journey.studio.movePageDown': 'Liiguta tahapoole',
'journey.studio.beta': 'Beeta',
'journey.studio.addProsCons': 'Plussid ja miinused',
'journey.studio.showMarks': 'Märgised',
'journey.studio.formatCustom': 'Kohandatud suurus',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Aktsent',
'studio.theme.token.accent2': 'Aktsent 2',
'studio.theme.token.accent3': 'Aktsent 3',
'studio.theme.accents': 'Aktsendid',
'studio.theme.fromPhotos': 'Värvid fotodelt',
'studio.theme.noPhotoColours': 'Fotodel pole värvi, mida võtta',
'studio.theme.fonts': 'Kirjatüübid',
+1 -2
View File
@@ -295,7 +295,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Carré 30 × 30 cm',
'journey.studio.pages': 'Pages', // same-as-en
'journey.studio.cover': 'Couverture',
'journey.studio.inspectorEmpty': 'Sélectionnez un élément de la page pour le modifier.',
'journey.studio.inspectorEmpty': 'Sélectionnez un élément',
'journey.studio.autoLayout': 'Mise en page auto',
'journey.studio.export': 'Exporter',
'journey.studio.day': 'JOUR {day}',
@@ -546,7 +546,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Supprimer la page',
'journey.studio.movePageUp': 'Déplacer avant',
'journey.studio.movePageDown': 'Déplacer après',
'journey.studio.beta': 'Bêta',
'journey.studio.addProsCons': 'Pour et contre',
'journey.studio.showMarks': 'Repères',
'journey.studio.formatCustom': 'Format libre',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Accent', // same-as-en
'studio.theme.token.accent2': 'Accent 2', // same-as-en
'studio.theme.token.accent3': 'Accent 3', // same-as-en
'studio.theme.accents': 'Accents', // same-as-en
'studio.theme.fromPhotos': 'Couleurs des photos',
'studio.theme.noPhotoColours': 'Les photos n’ont aucune couleur à reprendre',
'studio.theme.fonts': 'Polices',
+1 -2
View File
@@ -298,7 +298,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Τετράγωνο 30 × 30 εκ.',
'journey.studio.pages': 'Σελίδες',
'journey.studio.cover': 'Εξώφυλλο',
'journey.studio.inspectorEmpty': 'Επιλέξτε κάτι στη σελίδα για να το επεξεργαστείτε.',
'journey.studio.inspectorEmpty': 'Επιλέξτε κάτι για επεξεργασία',
'journey.studio.autoLayout': 'Αυτόματη διάταξη',
'journey.studio.export': 'Εξαγωγή',
'journey.studio.day': 'ΗΜΕΡΑ {day}',
@@ -552,7 +552,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Διαγραφή σελίδας',
'journey.studio.movePageUp': 'Μετακίνηση νωρίτερα',
'journey.studio.movePageDown': 'Μετακίνηση αργότερα',
'journey.studio.beta': 'Δοκιμαστική',
'journey.studio.addProsCons': 'Υπέρ & Κατά',
'journey.studio.showMarks': 'Σήματα',
'journey.studio.formatCustom': 'Δικό σας μέγεθος',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Τόνος',
'studio.theme.token.accent2': 'Τόνος 2',
'studio.theme.token.accent3': 'Τόνος 3',
'studio.theme.accents': 'Τόνοι',
'studio.theme.fromPhotos': 'Χρώματα από τις φωτογραφίες',
'studio.theme.noPhotoColours': 'Οι φωτογραφίες δεν έχουν χρώμα να πάρουμε',
'studio.theme.fonts': 'Γραμματοσειρές',
+1 -2
View File
@@ -294,7 +294,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Négyzet 30 × 30 cm',
'journey.studio.pages': 'Oldalak',
'journey.studio.cover': 'Borító',
'journey.studio.inspectorEmpty': 'Válassz ki valamit az oldalon a szerkesztéséhez.',
'journey.studio.inspectorEmpty': 'Válassz ki valamit',
'journey.studio.autoLayout': 'Automatikus elrendezés',
'journey.studio.export': 'Exportálás',
'journey.studio.day': '{day}. NAP',
@@ -543,7 +543,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Oldal törlése',
'journey.studio.movePageUp': 'Előrébb',
'journey.studio.movePageDown': 'Hátrébb',
'journey.studio.beta': 'Béta',
'journey.studio.addProsCons': 'Előnyök és hátrányok',
'journey.studio.showMarks': 'Jelek',
'journey.studio.formatCustom': 'Egyéni méret',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Kiemelés',
'studio.theme.token.accent2': 'Kiemelés 2',
'studio.theme.token.accent3': 'Kiemelés 3',
'studio.theme.accents': 'Kiemelések',
'studio.theme.fromPhotos': 'Színek a fotókból',
'studio.theme.noPhotoColours': 'A fotókon nincs átvehető szín',
'studio.theme.fonts': 'Betűtípusok',
+1 -2
View File
@@ -287,7 +287,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Persegi 30 × 30 cm',
'journey.studio.pages': 'Halaman',
'journey.studio.cover': 'Sampul',
'journey.studio.inspectorEmpty': 'Pilih sesuatu di halaman untuk mengeditnya.',
'journey.studio.inspectorEmpty': 'Pilih sesuatu untuk diedit',
'journey.studio.autoLayout': 'Tata letak otomatis',
'journey.studio.export': 'Ekspor',
'journey.studio.day': 'HARI {day}',
@@ -536,7 +536,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Hapus halaman',
'journey.studio.movePageUp': 'Pindah ke depan',
'journey.studio.movePageDown': 'Pindah ke belakang',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Pro & Kontra',
'journey.studio.showMarks': 'Tanda',
'journey.studio.formatCustom': 'Ukuran sendiri',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Aksen',
'studio.theme.token.accent2': 'Aksen 2',
'studio.theme.token.accent3': 'Aksen 3',
'studio.theme.accents': 'Aksen',
'studio.theme.fromPhotos': 'Warna dari foto',
'studio.theme.noPhotoColours': 'Foto tidak punya warna untuk diambil',
'studio.theme.fonts': 'Jenis huruf',
+1 -2
View File
@@ -294,7 +294,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Quadrato 30 × 30 cm',
'journey.studio.pages': 'Pagine',
'journey.studio.cover': 'Copertina',
'journey.studio.inspectorEmpty': 'Seleziona un elemento della pagina per modificarlo.',
'journey.studio.inspectorEmpty': 'Seleziona un elemento',
'journey.studio.autoLayout': 'Layout automatico',
'journey.studio.export': 'Esporta',
'journey.studio.day': 'GIORNO {day}',
@@ -545,7 +545,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Elimina pagina',
'journey.studio.movePageUp': 'Sposta prima',
'journey.studio.movePageDown': 'Sposta dopo',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Pro e contro',
'journey.studio.showMarks': 'Segni',
'journey.studio.formatCustom': 'Formato libero',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Accento',
'studio.theme.token.accent2': 'Accento 2',
'studio.theme.token.accent3': 'Accento 3',
'studio.theme.accents': 'Accenti',
'studio.theme.fromPhotos': 'Colori dalle foto',
'studio.theme.noPhotoColours': 'Le foto non hanno colori da prendere',
'studio.theme.fonts': 'Caratteri',
+1 -2
View File
@@ -286,7 +286,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': '正方形 30 × 30 cm',
'journey.studio.pages': 'ページ',
'journey.studio.cover': '表紙',
'journey.studio.inspectorEmpty': 'ページ上の要素を選択すると編集できます。',
'journey.studio.inspectorEmpty': '編集する要素を選択',
'journey.studio.autoLayout': '自動レイアウト',
'journey.studio.export': 'エクスポート',
'journey.studio.day': '{day}日目',
@@ -535,7 +535,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'ページを削除',
'journey.studio.movePageUp': '前へ移動',
'journey.studio.movePageDown': '後ろへ移動',
'journey.studio.beta': 'ベータ',
'journey.studio.addProsCons': '良かった点・気になった点',
'journey.studio.showMarks': '記号',
'journey.studio.formatCustom': 'カスタムサイズ',
+1
View File
@@ -37,6 +37,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'アクセント',
'studio.theme.token.accent2': 'アクセント 2',
'studio.theme.token.accent3': 'アクセント 3',
'studio.theme.accents': 'アクセント',
'studio.theme.fromPhotos': '写真から色を取る',
'studio.theme.noPhotoColours': '写真から取れる色がありません',
'studio.theme.fonts': '書体',
+1 -2
View File
@@ -283,7 +283,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': '정사각형 30 × 30 cm',
'journey.studio.pages': '페이지',
'journey.studio.cover': '표지',
'journey.studio.inspectorEmpty': '페이지에서 편집할 항목을 선택하세요.',
'journey.studio.inspectorEmpty': '편집할 항목을 선택하세요',
'journey.studio.autoLayout': '자동 레이아웃',
'journey.studio.export': '내보내기',
'journey.studio.day': '{day}일차',
@@ -531,7 +531,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': '페이지 삭제',
'journey.studio.movePageUp': '앞으로 이동',
'journey.studio.movePageDown': '뒤로 이동',
'journey.studio.beta': '베타',
'journey.studio.addProsCons': '장단점',
'journey.studio.showMarks': '기호',
'journey.studio.formatCustom': '사용자 지정 크기',
+1
View File
@@ -37,6 +37,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': '포인트',
'studio.theme.token.accent2': '포인트 2',
'studio.theme.token.accent3': '포인트 3',
'studio.theme.accents': '포인트',
'studio.theme.fromPhotos': '사진에서 색 가져오기',
'studio.theme.noPhotoColours': '사진에서 가져올 색이 없습니다',
'studio.theme.fonts': '글꼴',
+1 -2
View File
@@ -296,7 +296,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Vierkant 30 × 30 cm',
'journey.studio.pages': "Pagina's",
'journey.studio.cover': 'Omslag',
'journey.studio.inspectorEmpty': 'Selecteer iets op de pagina om het te bewerken.',
'journey.studio.inspectorEmpty': 'Selecteer iets om te bewerken',
'journey.studio.autoLayout': 'Automatische opmaak',
'journey.studio.export': 'Exporteren',
'journey.studio.day': 'DAG {day}',
@@ -547,7 +547,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Pagina verwijderen',
'journey.studio.movePageUp': 'Naar voren',
'journey.studio.movePageDown': 'Naar achteren',
'journey.studio.beta': 'Bèta',
'journey.studio.addProsCons': 'Voor- & nadelen',
'journey.studio.showMarks': 'Tekens',
'journey.studio.formatCustom': 'Eigen formaat',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Accent', // same-as-en
'studio.theme.token.accent2': 'Accent 2', // same-as-en
'studio.theme.token.accent3': 'Accent 3', // same-as-en
'studio.theme.accents': 'Accenten',
'studio.theme.fromPhotos': 'Kleuren uit de foto’s',
'studio.theme.noPhotoColours': 'De foto’s hebben geen kleur om over te nemen',
'studio.theme.fonts': 'Lettertypen',
+1 -2
View File
@@ -308,7 +308,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Kwadrat 30 × 30 cm',
'journey.studio.pages': 'Strony',
'journey.studio.cover': 'Okładka',
'journey.studio.inspectorEmpty': 'Zaznacz coś na stronie, aby to edytować.',
'journey.studio.inspectorEmpty': 'Zaznacz coś do edycji',
'journey.studio.autoLayout': 'Układ automatyczny',
'journey.studio.export': 'Eksportuj',
'journey.studio.day': 'DZIEŃ {day}',
@@ -563,7 +563,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Usuń stronę',
'journey.studio.movePageUp': 'Przenieś wcześniej',
'journey.studio.movePageDown': 'Przenieś później',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Zalety i wady',
'journey.studio.showMarks': 'Znaki',
'journey.studio.formatCustom': 'Własny format',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Akcent',
'studio.theme.token.accent2': 'Akcent 2',
'studio.theme.token.accent3': 'Akcent 3',
'studio.theme.accents': 'Akcenty',
'studio.theme.fromPhotos': 'Kolory ze zdjęć',
'studio.theme.noPhotoColours': 'Zdjęcia nie mają koloru do przejęcia',
'studio.theme.fonts': 'Kroje pisma',
+1 -2
View File
@@ -308,7 +308,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Квадрат 30 × 30 см',
'journey.studio.pages': 'Страницы',
'journey.studio.cover': 'Обложка',
'journey.studio.inspectorEmpty': 'Выберите что-нибудь на странице, чтобы изменить это.',
'journey.studio.inspectorEmpty': 'Выберите элемент',
'journey.studio.autoLayout': 'Автомакет',
'journey.studio.export': 'Экспорт',
'journey.studio.day': 'ДЕНЬ {day}',
@@ -563,7 +563,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Удалить разворот',
'journey.studio.movePageUp': 'Переместить раньше',
'journey.studio.movePageDown': 'Переместить позже',
'journey.studio.beta': 'Бета',
'journey.studio.addProsCons': 'Плюсы и минусы',
'journey.studio.showMarks': 'Знаки',
'journey.studio.formatCustom': 'Свой формат',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Акцент',
'studio.theme.token.accent2': 'Акцент 2',
'studio.theme.token.accent3': 'Акцент 3',
'studio.theme.accents': 'Акценты',
'studio.theme.fromPhotos': 'Цвета из фотографий',
'studio.theme.noPhotoColours': 'На фотографиях нет цвета, который можно взять',
'studio.theme.fonts': 'Шрифты',
+1 -2
View File
@@ -300,7 +300,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Štvorec 30 × 30 cm',
'journey.studio.pages': 'Strany',
'journey.studio.cover': 'Obal',
'journey.studio.inspectorEmpty': 'Vyberte niečo na strane a upravte to.',
'journey.studio.inspectorEmpty': 'Vyberte niečo na úpravu',
'journey.studio.autoLayout': 'Automatické rozloženie',
'journey.studio.export': 'Exportovať',
'journey.studio.day': 'DEŇ {day}',
@@ -555,7 +555,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Odstrániť dvojstranu',
'journey.studio.movePageUp': 'Posunúť dopredu',
'journey.studio.movePageDown': 'Posunúť dozadu',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Klady a zápory',
'journey.studio.showMarks': 'Značky',
'journey.studio.formatCustom': 'Vlastná veľkosť',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Akcent',
'studio.theme.token.accent2': 'Akcent 2',
'studio.theme.token.accent3': 'Akcent 3',
'studio.theme.accents': 'Akcenty',
'studio.theme.fromPhotos': 'Farby z fotiek',
'studio.theme.noPhotoColours': 'Fotky nemajú žiadnu farbu na prevzatie',
'studio.theme.fonts': 'Písma',
+1 -2
View File
@@ -294,7 +294,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Kvadrat 30 × 30 cm',
'journey.studio.pages': 'Sidor',
'journey.studio.cover': 'Omslag',
'journey.studio.inspectorEmpty': 'Markera något på sidan för att redigera det.',
'journey.studio.inspectorEmpty': 'Markera något att redigera',
'journey.studio.autoLayout': 'Automatisk layout',
'journey.studio.export': 'Exportera',
'journey.studio.day': 'DAG {day}',
@@ -543,7 +543,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Ta bort uppslag',
'journey.studio.movePageUp': 'Flytta tidigare',
'journey.studio.movePageDown': 'Flytta senare',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'För- och nackdelar',
'journey.studio.showMarks': 'Tecken',
'journey.studio.formatCustom': 'Eget format',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Accent', // same-as-en
'studio.theme.token.accent2': 'Accent 2', // same-as-en
'studio.theme.token.accent3': 'Accent 3', // same-as-en
'studio.theme.accents': 'Accenter',
'studio.theme.fromPhotos': 'Färger från bilderna',
'studio.theme.noPhotoColours': 'Bilderna har ingen färg att ta',
'studio.theme.fonts': 'Typsnitt',
+1 -2
View File
@@ -283,7 +283,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'สี่เหลี่ยมจัตุรัส 30 × 30 ซม',
'journey.studio.pages': 'หน้า',
'journey.studio.cover': 'ปก',
'journey.studio.inspectorEmpty': 'เลือกบางอย่างบนเพจเพื่อแก้ไข',
'journey.studio.inspectorEmpty': 'เลือกสิ่งที่จะแก้ไข',
'journey.studio.autoLayout': 'เค้าโครงอัตโนมัติ',
'journey.studio.export': 'ส่งออก',
'journey.studio.day': 'วันที่ {day}',
@@ -529,7 +529,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'ลบสเปรด',
'journey.studio.movePageUp': 'ย้ายมาก่อน',
'journey.studio.movePageDown': 'ย้ายในภายหลัง',
'journey.studio.beta': 'เบต้า',
'journey.studio.addProsCons': 'ข้อดีข้อเสีย',
'journey.studio.showMarks': 'เครื่องหมาย',
'journey.studio.formatCustom': 'ขนาดที่กำหนดเอง',
+1
View File
@@ -37,6 +37,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'สีเน้น',
'studio.theme.token.accent2': 'สีเน้น 2',
'studio.theme.token.accent3': 'สีเน้น 3',
'studio.theme.accents': 'สีเน้น',
'studio.theme.fromPhotos': 'สีจากรูปภาพ',
'studio.theme.noPhotoColours': 'รูปภาพไม่มีสีให้นำมาใช้',
'studio.theme.fonts': 'แบบอักษร',
+1 -2
View File
@@ -293,7 +293,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Kare 30 × 30 cm',
'journey.studio.pages': 'Sayfalar',
'journey.studio.cover': 'Kapak',
'journey.studio.inspectorEmpty': 'Düzenlemek için sayfada bir öğe seç.',
'journey.studio.inspectorEmpty': 'Düzenlemek için bir öğe seç',
'journey.studio.autoLayout': 'Otomatik yerleşim',
'journey.studio.export': 'Dışa aktar',
'journey.studio.day': '{day}. GÜN',
@@ -546,7 +546,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Sayfayı sil',
'journey.studio.movePageUp': 'Öne al',
'journey.studio.movePageDown': 'Arkaya al',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Artılar ve Eksiler',
'journey.studio.showMarks': 'İşaretler',
'journey.studio.formatCustom': 'Özel boyut',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Vurgu',
'studio.theme.token.accent2': 'Vurgu 2',
'studio.theme.token.accent3': 'Vurgu 3',
'studio.theme.accents': 'Vurgular',
'studio.theme.fromPhotos': 'Fotoğraflardan renkler',
'studio.theme.noPhotoColours': 'Fotoğraflarda alınacak renk yok',
'studio.theme.fonts': 'Yazı tipleri',
+1 -2
View File
@@ -309,7 +309,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Квадрат 30 × 30 см',
'journey.studio.pages': 'Сторінки',
'journey.studio.cover': 'Обкладинка',
'journey.studio.inspectorEmpty': 'Вибери щось на сторінці, щоб редагувати.',
'journey.studio.inspectorEmpty': 'Виберіть елемент',
'journey.studio.autoLayout': 'Автомакет',
'journey.studio.export': 'Експорт',
'journey.studio.day': 'ДЕНЬ {day}',
@@ -566,7 +566,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Видалити розворот',
'journey.studio.movePageUp': 'Перемістити раніше',
'journey.studio.movePageDown': 'Перемістити пізніше',
'journey.studio.beta': 'Бета',
'journey.studio.addProsCons': 'Плюси й мінуси',
'journey.studio.showMarks': 'Знаки',
'journey.studio.formatCustom': 'Свій формат',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Акцент',
'studio.theme.token.accent2': 'Акцент 2',
'studio.theme.token.accent3': 'Акцент 3',
'studio.theme.accents': 'Акценти',
'studio.theme.fromPhotos': 'Кольори з фотографій',
'studio.theme.noPhotoColours': 'На фотографіях немає кольору, який можна взяти',
'studio.theme.fonts': 'Шрифти',
+1 -2
View File
@@ -288,7 +288,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': 'Vuông 30 × 30 cm',
'journey.studio.pages': 'Trang',
'journey.studio.cover': 'Bìa',
'journey.studio.inspectorEmpty': 'Chọn một thứ trên trang để chỉnh sửa.',
'journey.studio.inspectorEmpty': 'Chọn một mục để sửa',
'journey.studio.autoLayout': 'Bố cục tự động',
'journey.studio.export': 'Xuất',
'journey.studio.day': 'NGÀY {day}',
@@ -535,7 +535,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': 'Xóa trang',
'journey.studio.movePageUp': 'Chuyển lên trước',
'journey.studio.movePageDown': 'Chuyển xuống sau',
'journey.studio.beta': 'Beta', // same-as-en
'journey.studio.addProsCons': 'Ưu và nhược điểm',
'journey.studio.showMarks': 'Dấu',
'journey.studio.formatCustom': 'Kích thước riêng',
+1
View File
@@ -38,6 +38,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': 'Điểm nhấn',
'studio.theme.token.accent2': 'Điểm nhấn 2',
'studio.theme.token.accent3': 'Điểm nhấn 3',
'studio.theme.accents': 'Điểm nhấn',
'studio.theme.fromPhotos': 'Màu từ ảnh',
'studio.theme.noPhotoColours': 'Ảnh không có màu nào để lấy',
'studio.theme.fonts': 'Kiểu chữ',
+1 -2
View File
@@ -281,7 +281,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': '正方形 30 × 30 公分',
'journey.studio.pages': '頁面',
'journey.studio.cover': '封面',
'journey.studio.inspectorEmpty': '請在頁面上選取要編輯的項目。',
'journey.studio.inspectorEmpty': '選取要編輯的項目',
'journey.studio.autoLayout': '自動排版',
'journey.studio.export': '匯出',
'journey.studio.day': '第{day}天',
@@ -525,7 +525,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': '刪除頁面',
'journey.studio.movePageUp': '前移',
'journey.studio.movePageDown': '後移',
'journey.studio.beta': '測試版',
'journey.studio.addProsCons': '優缺點',
'journey.studio.showMarks': '符號',
'journey.studio.formatCustom': '自訂尺寸',
+1
View File
@@ -37,6 +37,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': '強調色',
'studio.theme.token.accent2': '強調色 2',
'studio.theme.token.accent3': '強調色 3',
'studio.theme.accents': '強調色',
'studio.theme.fromPhotos': '從照片取色',
'studio.theme.noPhotoColours': '照片中沒有可取的顏色',
'studio.theme.fonts': '字體',
+1 -2
View File
@@ -281,7 +281,7 @@ const journey: TranslationStrings = {
'journey.studio.formatSquare30': '方形 30 × 30 厘米',
'journey.studio.pages': '页面',
'journey.studio.cover': '封面',
'journey.studio.inspectorEmpty': '在页面上选择内容即可编辑。',
'journey.studio.inspectorEmpty': '选择要编辑的内容',
'journey.studio.autoLayout': '自动排版',
'journey.studio.export': '导出',
'journey.studio.day': '第{day}天',
@@ -525,7 +525,6 @@ const journey: TranslationStrings = {
'journey.studio.deletePage': '删除页面',
'journey.studio.movePageUp': '前移',
'journey.studio.movePageDown': '后移',
'journey.studio.beta': '测试版',
'journey.studio.addProsCons': '优缺点',
'journey.studio.showMarks': '符号',
'journey.studio.formatCustom': '自定义尺寸',
+1
View File
@@ -37,6 +37,7 @@ const studioThemes: TranslationStrings = {
'studio.theme.token.accent': '强调色',
'studio.theme.token.accent2': '强调色 2',
'studio.theme.token.accent3': '强调色 3',
'studio.theme.accents': '强调色',
'studio.theme.fromPhotos': '从照片取色',
'studio.theme.noPhotoColours': '照片中没有可取的颜色',
'studio.theme.fonts': '字体',