mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
feat(studio): 34 typefaces with real weights and italics, a categorised font picker
The book catalogue grows from 7 families to 34, all self-hosted through @fontsource under the SIL OFL 1.1 or the Apache License 2.0: condensed and poster faces, heavy retro display, elegant display serifs, Montserrat, twelve scripts and DM Mono. Weights run from 100 to 900, each family offering only the weights it ships, and the serifs gain real italics. The catalogue lives in @trek/shared (book-fonts.data.ts) with each family's roles (display, heading, body, script, mono), weights, italics and letter width, so the Studio, the findings, the design engine and the assistant read one list. Page operations and the design engine set a weight a family lacks at its nearest one, describe_book_page names the role and the weight, and font_mix no longer counts scripts and mono faces, warning above three. The Studio gets StudioFontPicker: the current family set in itself, a searchable list grouped by kind with every family in its own face and the book's own families first, and a weight slider over the weights the family has. Faces load only when used; the assistant's page snapshot embeds only the faces a page draws, the service worker installs only the latin files of the original seven families, and the faces of an opened book are cached for offline use. BOOK_SCHEMA_REV goes to 2.
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
bookStrokeStyleSchema,
|
||||
bookTextAlignSchema,
|
||||
bookTypesetFontSchema,
|
||||
bookWeightSchema,
|
||||
} from './book-op-enums';
|
||||
import { bookElementIdSchema, bookGridIdSchema, bookSpreadIdSchema } from './book-op-ids';
|
||||
import {
|
||||
@@ -99,7 +100,7 @@ const PHOTO_PROPS = {
|
||||
export const TEXT_STYLE_FIELDS = {
|
||||
font: bookFontIdSchema.optional(),
|
||||
size: text.size.unwrap().optional(),
|
||||
weight: text.weight.unwrap().optional(),
|
||||
weight: bookWeightSchema.optional(),
|
||||
italic: text.italic.unwrap().optional(),
|
||||
align: bookTextAlignSchema.optional(),
|
||||
leading: text.leading.unwrap().optional(),
|
||||
|
||||
@@ -140,6 +140,12 @@ describe('book level and the page in words', () => {
|
||||
const mixOf = (pages: typeof fonts) => bookLevelFindings(bookOf(pages)).filter((f) => f.code === 'font_mix');
|
||||
expect(mixOf(fonts.slice(0, 3))).toEqual([]);
|
||||
expect(mixOf(fonts.slice(0, 2))).toEqual([]);
|
||||
// A script laid over a title and a mono line on a ticket dress a page; they are not a fourth voice.
|
||||
const dressed = ['caveat', 'mono'].map((font, i) => [
|
||||
text(`d-${i}`, 'Hi', { x: 230, y: 40, w: 60, h: 10 }, { font }),
|
||||
]);
|
||||
expect(mixOf([...fonts.slice(0, 3), ...dressed])).toEqual([]);
|
||||
expect(mixOf([...fonts, ...dressed])[0]?.message).toMatch(/4 typefaces \(bebas, playfair, sans, serif\)/);
|
||||
});
|
||||
|
||||
it('BOOKQA-010: notes colours outside the book own and the same page three times running', () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { compareCodeUnits } from '../common/compare';
|
||||
import { contrastOfLuminance, deltaE, greyLuminance, luminance, parseHex, requiredContrast } from './book-colour';
|
||||
import { bookFontCountsInMix, bookFontsUsed } from './book-fonts';
|
||||
import type { BookPhotoInfo } from './book-ops-core';
|
||||
import { BOOK_OP_LIMITS, type BookFinding } from './book-ops.schema';
|
||||
import type { BookDocument, BookElement, BookFrame, BookPageSetup, BookPhotoElement, BookSpread } from './book.schema';
|
||||
@@ -306,16 +307,13 @@ export function designFindings(
|
||||
return sortBookFindings(ctx.out);
|
||||
}
|
||||
|
||||
/** The typefaces a book is set in: its texts, its travel elements and its page numbers when shown. */
|
||||
/**
|
||||
* The typefaces that make a book's voice: its texts, its travel elements and
|
||||
* its page numbers when shown, leaving out the scripts and mono faces, which
|
||||
* dress a page rather than set it.
|
||||
*/
|
||||
function familiesOf(doc: BookDocument): string[] {
|
||||
const used = new Set<string>();
|
||||
for (const spread of doc.spreads) {
|
||||
for (const el of spread.elements) {
|
||||
if ('font' in el && (el.kind !== 'text' || el.text.trim())) used.add(el.font);
|
||||
}
|
||||
}
|
||||
if (doc.page.pageNumbers.show) used.add(doc.page.pageNumbers.font);
|
||||
return [...used].sort(compareCodeUnits);
|
||||
return bookFontsUsed(doc).filter(bookFontCountsInMix).sort(compareCodeUnits);
|
||||
}
|
||||
|
||||
/** The colours a book paints with: its type and its filled shapes, with how often each is used. */
|
||||
@@ -342,10 +340,10 @@ function signatureOf(spread: BookSpread): string {
|
||||
|
||||
/**
|
||||
* How many typefaces a book may be set in before it reads as a type sampler:
|
||||
* a display face, a body face and one more, a script laid over a title or a
|
||||
* face for figures. Two was the rule while the book had seven faces and no
|
||||
* scripts, and it told every lively book that set its title apart to stop.
|
||||
* Once fonts carry roles, script and mono faces will not count towards it.
|
||||
* a display face, a body face and one more for figures or a subtitle. Two was
|
||||
* the rule while the book had seven faces and no scripts, and it told every
|
||||
* lively book that set its title apart to stop. Script and mono faces do not
|
||||
* count towards it (`bookFontCountsInMix`).
|
||||
*/
|
||||
const MAX_BOOK_FAMILIES = 3;
|
||||
|
||||
@@ -358,7 +356,7 @@ export function bookLevelFindings(doc: BookDocument): BookFinding[] {
|
||||
code: 'font_mix',
|
||||
severity: 'warn',
|
||||
elementIds: [],
|
||||
message: `the book is set in ${families.length} typefaces (${families.join(', ')}); keep to three: a display face, a body face and one more`,
|
||||
message: `the book is set in ${families.length} typefaces (${families.join(', ')}); keep to three: a display face, a body face and one more; scripts and mono faces do not count`,
|
||||
});
|
||||
}
|
||||
const colours = [...coloursOf(doc)].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { bookVisiblePart, sortBookFindings } from './book-findings-design';
|
||||
import { bookFontFace } from './book-fonts';
|
||||
import type { BookPhotoInfo } from './book-ops-core';
|
||||
import type { BookFinding } from './book-ops.schema';
|
||||
import type { BookElement, BookPageSetup, BookPhotoElement, BookSpread, BookTextElement } from './book.schema';
|
||||
@@ -66,16 +67,8 @@ export function photoDpi(el: BookPhotoElement, info: BookPhotoInfo | undefined):
|
||||
return Math.round(25.4 / mmPerPx);
|
||||
}
|
||||
|
||||
/** Average letter width per em at weight 400, per family: measured on story text as the Studio sets each family. */
|
||||
const LETTER_EM: Record<BookTextElement['font'], number> = {
|
||||
sans: 0.507,
|
||||
inter: 0.478,
|
||||
serif: 0.469,
|
||||
garamond: 0.383,
|
||||
playfair: 0.454,
|
||||
display: 0.492,
|
||||
bebas: 0.339,
|
||||
};
|
||||
/** Average letter width per em at weight 400, per family: the catalogue's measure (book-fonts.data.ts). */
|
||||
const letterEm = (font: string) => bookFontFace(font)?.letterEm ?? 0.5;
|
||||
|
||||
/**
|
||||
* The headroom on letter width the design engine sizes a text frame with. A
|
||||
@@ -121,7 +114,7 @@ function wrappedLines(para: string, perLine: number): number {
|
||||
export function textHeightNeeded(el: BookTextElement, headroom = 1): number {
|
||||
const sizeMm = (orDefault(el.size, 11) * 25.4) / 72;
|
||||
const tracking = orDefault(el.tracking, 0);
|
||||
const letter = sizeMm * ((LETTER_EM[el.font] ?? 0.5) * (el.weight >= 600 ? 1.06 : 1) * headroom + tracking);
|
||||
const letter = sizeMm * (letterEm(el.font) * (el.weight >= 600 ? 1.06 : 1) * headroom + tracking);
|
||||
const perLine = Math.max(1, Math.floor(el.frame.w / Math.max(letter, 0.1)));
|
||||
const lines = el.text.split('\n').reduce((sum, para) => sum + wrappedLines(para.trimEnd(), perLine), 0);
|
||||
return lines * sizeMm * orDefault(el.leading, 1.45);
|
||||
|
||||
@@ -0,0 +1,481 @@
|
||||
/**
|
||||
* The typefaces a book may be set in, and what each of them is.
|
||||
*
|
||||
* One catalogue for every reader of a book: the schema takes its ids, the
|
||||
* Studio draws its picker and its stacks from it, the findings count its
|
||||
* roles, the design engine checks its weights, and the assistant reads the
|
||||
* same lines in the tool schemas. A family named in one of those places and
|
||||
* missing from another is how five of the first seven fonts shipped without
|
||||
* their faces (#2183), so nothing here is copied anywhere else.
|
||||
*
|
||||
* Every family is self-hosted through @fontsource and licensed under the SIL
|
||||
* Open Font License 1.1 or the Apache License 2.0 (the typefaces in NOTICE.md),
|
||||
* which both allow embedding in a printed or exported book.
|
||||
*
|
||||
* This file holds data only, so book.schema.ts can take the ids from it
|
||||
* without importing the logic in book-fonts.ts.
|
||||
*/
|
||||
|
||||
/** Every weight CSS names. A family offers the ones it really ships. */
|
||||
export const BOOK_WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900] as const;
|
||||
export type BookWeight = (typeof BOOK_WEIGHTS)[number];
|
||||
|
||||
/**
|
||||
* What a family is for, most suited first.
|
||||
*
|
||||
* `display` sets a cover title or a poster word, `heading` a page's title,
|
||||
* `body` running text at 9 to 11 pt. A `script` is laid over a title or a
|
||||
* photo, a `mono` sets tickets and coordinates. Neither of the last two
|
||||
* counts towards the faces a book is set in (`font_mix`), because they are
|
||||
* ornaments of a page rather than its voice.
|
||||
*/
|
||||
export const BOOK_FONT_ROLES = ['display', 'heading', 'body', 'script', 'mono'] as const;
|
||||
export type BookFontRole = (typeof BOOK_FONT_ROLES)[number];
|
||||
|
||||
/** The picker's groups, in the order it shows them. */
|
||||
export const BOOK_FONT_CATEGORIES = ['sans', 'serif', 'elegant', 'condensed', 'retro', 'script', 'mono'] as const;
|
||||
export type BookFontCategory = (typeof BOOK_FONT_CATEGORIES)[number];
|
||||
|
||||
export interface BookFontFace {
|
||||
/** What it is called, shown as itself in the picker. */
|
||||
name: string;
|
||||
/** The CSS stack. Ends in a generic, never in a font that is not bundled. */
|
||||
stack: string;
|
||||
/** The @fontsource package the faces come from. */
|
||||
pkg: string;
|
||||
category: BookFontCategory;
|
||||
/** What it suits, the first being what it is for. */
|
||||
roles: readonly BookFontRole[];
|
||||
/**
|
||||
* Weights actually loaded. Asking for 700 on a family that ships one weight
|
||||
* gets a synthesised bold, which prints as a smeared version of the regular.
|
||||
*/
|
||||
weights: readonly BookWeight[];
|
||||
/** The weights it has a real italic for. Any other italic is the browser's slant. */
|
||||
italics: readonly BookWeight[];
|
||||
/**
|
||||
* Average letter width per em at weight 400, measured on story text from
|
||||
* the face's own advance widths, for the text measure in book-findings.ts.
|
||||
*/
|
||||
letterEm: number;
|
||||
/** Set in capitals whatever is typed. */
|
||||
caps?: true;
|
||||
}
|
||||
|
||||
const ALL = BOOK_WEIGHTS;
|
||||
const ONE = [400] as const;
|
||||
const MID = [400, 500, 600, 700] as const;
|
||||
|
||||
/**
|
||||
* The families. `sans`, `serif` and `display` are the three the format started
|
||||
* with and keep their names, because documents already contain them.
|
||||
*/
|
||||
export const BOOK_FONT_FACES = {
|
||||
// ── sans ──
|
||||
sans: {
|
||||
name: 'Poppins',
|
||||
stack: '"Poppins", system-ui, sans-serif',
|
||||
pkg: 'poppins',
|
||||
category: 'sans',
|
||||
roles: ['heading', 'body', 'display'],
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.507,
|
||||
},
|
||||
inter: {
|
||||
name: 'Inter',
|
||||
stack: '"Inter", system-ui, sans-serif',
|
||||
pkg: 'inter',
|
||||
category: 'sans',
|
||||
roles: ['body', 'heading'],
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.478,
|
||||
},
|
||||
montserrat: {
|
||||
name: 'Montserrat',
|
||||
stack: '"Montserrat", system-ui, sans-serif',
|
||||
pkg: 'montserrat',
|
||||
category: 'sans',
|
||||
roles: ['heading', 'body', 'display'],
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.507,
|
||||
},
|
||||
// ── serif ──
|
||||
serif: {
|
||||
name: 'Lora',
|
||||
stack: '"Lora", Georgia, serif',
|
||||
pkg: 'lora',
|
||||
category: 'serif',
|
||||
roles: ['body', 'heading'],
|
||||
weights: MID,
|
||||
italics: MID,
|
||||
letterEm: 0.469,
|
||||
},
|
||||
garamond: {
|
||||
name: 'EB Garamond',
|
||||
stack: '"EB Garamond", Georgia, serif',
|
||||
pkg: 'eb-garamond',
|
||||
category: 'serif',
|
||||
roles: ['body', 'heading'],
|
||||
weights: [400, 500, 600, 700, 800],
|
||||
italics: [400, 500, 600, 700, 800],
|
||||
letterEm: 0.383,
|
||||
},
|
||||
// ── elegant display serifs ──
|
||||
playfair: {
|
||||
name: 'Playfair Display',
|
||||
stack: '"Playfair Display", Georgia, serif',
|
||||
pkg: 'playfair-display',
|
||||
category: 'elegant',
|
||||
roles: ['display', 'heading'],
|
||||
weights: [400, 500, 600, 700, 800, 900],
|
||||
italics: [400, 500, 600, 700, 800, 900],
|
||||
letterEm: 0.454,
|
||||
},
|
||||
fraunces: {
|
||||
name: 'Fraunces',
|
||||
stack: '"Fraunces", Georgia, serif',
|
||||
pkg: 'fraunces',
|
||||
category: 'elegant',
|
||||
roles: ['display', 'heading', 'body'],
|
||||
weights: ALL,
|
||||
italics: ALL,
|
||||
letterEm: 0.467,
|
||||
},
|
||||
cormorant: {
|
||||
name: 'Cormorant Garamond',
|
||||
stack: '"Cormorant Garamond", Georgia, serif',
|
||||
pkg: 'cormorant-garamond',
|
||||
category: 'elegant',
|
||||
roles: ['display', 'heading'],
|
||||
weights: [300, 400, 500, 600, 700],
|
||||
italics: [300, 400, 500, 600, 700],
|
||||
letterEm: 0.393,
|
||||
},
|
||||
dmserif: {
|
||||
name: 'DM Serif Display',
|
||||
stack: '"DM Serif Display", Georgia, serif',
|
||||
pkg: 'dm-serif-display',
|
||||
category: 'elegant',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: ONE,
|
||||
letterEm: 0.448,
|
||||
},
|
||||
bodoni: {
|
||||
name: 'Bodoni Moda',
|
||||
stack: '"Bodoni Moda", Georgia, serif',
|
||||
pkg: 'bodoni-moda',
|
||||
category: 'elegant',
|
||||
roles: ['display', 'heading'],
|
||||
weights: [400, 500, 600, 700, 800, 900],
|
||||
italics: [400, 500, 600, 700, 800, 900],
|
||||
letterEm: 0.463,
|
||||
},
|
||||
// ── condensed and poster ──
|
||||
bebas: {
|
||||
name: 'Bebas Neue',
|
||||
stack: '"Bebas Neue", Impact, sans-serif',
|
||||
pkg: 'bebas-neue',
|
||||
category: 'condensed',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.339,
|
||||
caps: true,
|
||||
},
|
||||
anton: {
|
||||
name: 'Anton',
|
||||
stack: '"Anton", Impact, sans-serif',
|
||||
pkg: 'anton',
|
||||
category: 'condensed',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.409,
|
||||
},
|
||||
oswald: {
|
||||
name: 'Oswald',
|
||||
stack: '"Oswald", Impact, sans-serif',
|
||||
pkg: 'oswald',
|
||||
category: 'condensed',
|
||||
roles: ['display', 'heading'],
|
||||
weights: [200, 300, 400, 500, 600, 700],
|
||||
italics: [],
|
||||
letterEm: 0.365,
|
||||
},
|
||||
league: {
|
||||
name: 'League Gothic',
|
||||
stack: '"League Gothic", Impact, sans-serif',
|
||||
pkg: 'league-gothic',
|
||||
category: 'condensed',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.266,
|
||||
},
|
||||
shoulders: {
|
||||
name: 'Big Shoulders Display',
|
||||
stack: '"Big Shoulders Display", Impact, sans-serif',
|
||||
pkg: 'big-shoulders-display',
|
||||
category: 'condensed',
|
||||
roles: ['display', 'heading'],
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.304,
|
||||
},
|
||||
archivo: {
|
||||
name: 'Archivo Black',
|
||||
stack: '"Archivo Black", Impact, sans-serif',
|
||||
pkg: 'archivo-black',
|
||||
category: 'condensed',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.557,
|
||||
},
|
||||
// ── heavy and retro display ──
|
||||
display: {
|
||||
name: 'MuseoModerno',
|
||||
stack: '"MuseoModerno", "Poppins", system-ui, sans-serif',
|
||||
pkg: 'museomoderno',
|
||||
category: 'retro',
|
||||
roles: ['display', 'heading'],
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.492,
|
||||
},
|
||||
shrikhand: {
|
||||
name: 'Shrikhand',
|
||||
stack: '"Shrikhand", system-ui, sans-serif',
|
||||
pkg: 'shrikhand',
|
||||
category: 'retro',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.529,
|
||||
},
|
||||
alfa: {
|
||||
name: 'Alfa Slab One',
|
||||
stack: '"Alfa Slab One", Georgia, serif',
|
||||
pkg: 'alfa-slab-one',
|
||||
category: 'retro',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.54,
|
||||
},
|
||||
bricolage: {
|
||||
name: 'Bricolage Grotesque',
|
||||
stack: '"Bricolage Grotesque", system-ui, sans-serif',
|
||||
pkg: 'bricolage-grotesque',
|
||||
category: 'retro',
|
||||
roles: ['display', 'heading', 'body'],
|
||||
weights: [200, 300, 400, 500, 600, 700, 800],
|
||||
italics: [],
|
||||
letterEm: 0.471,
|
||||
},
|
||||
abril: {
|
||||
name: 'Abril Fatface',
|
||||
stack: '"Abril Fatface", Georgia, serif',
|
||||
pkg: 'abril-fatface',
|
||||
category: 'retro',
|
||||
roles: ['display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.46,
|
||||
},
|
||||
// ── script and handwriting ──
|
||||
caveat: {
|
||||
name: 'Caveat',
|
||||
stack: '"Caveat", cursive',
|
||||
pkg: 'caveat',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: MID,
|
||||
italics: [],
|
||||
letterEm: 0.344,
|
||||
},
|
||||
dancing: {
|
||||
name: 'Dancing Script',
|
||||
stack: '"Dancing Script", cursive',
|
||||
pkg: 'dancing-script',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: MID,
|
||||
italics: [],
|
||||
letterEm: 0.363,
|
||||
},
|
||||
pacifico: {
|
||||
name: 'Pacifico',
|
||||
stack: '"Pacifico", cursive',
|
||||
pkg: 'pacifico',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.419,
|
||||
},
|
||||
vibes: {
|
||||
name: 'Great Vibes',
|
||||
stack: '"Great Vibes", cursive',
|
||||
pkg: 'great-vibes',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.284,
|
||||
},
|
||||
sacramento: {
|
||||
name: 'Sacramento',
|
||||
stack: '"Sacramento", cursive',
|
||||
pkg: 'sacramento',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.308,
|
||||
},
|
||||
kaushan: {
|
||||
name: 'Kaushan Script',
|
||||
stack: '"Kaushan Script", cursive',
|
||||
pkg: 'kaushan-script',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.396,
|
||||
},
|
||||
allura: {
|
||||
name: 'Allura',
|
||||
stack: '"Allura", cursive',
|
||||
pkg: 'allura',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.33,
|
||||
},
|
||||
parisienne: {
|
||||
name: 'Parisienne',
|
||||
stack: '"Parisienne", cursive',
|
||||
pkg: 'parisienne',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.361,
|
||||
},
|
||||
satisfy: {
|
||||
name: 'Satisfy',
|
||||
stack: '"Satisfy", cursive',
|
||||
pkg: 'satisfy',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.382,
|
||||
},
|
||||
yellowtail: {
|
||||
name: 'Yellowtail',
|
||||
stack: '"Yellowtail", cursive',
|
||||
pkg: 'yellowtail',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.333,
|
||||
},
|
||||
homemade: {
|
||||
name: 'Homemade Apple',
|
||||
stack: '"Homemade Apple", cursive',
|
||||
pkg: 'homemade-apple',
|
||||
category: 'script',
|
||||
roles: ['script'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.561,
|
||||
},
|
||||
marker: {
|
||||
name: 'Permanent Marker',
|
||||
stack: '"Permanent Marker", cursive',
|
||||
pkg: 'permanent-marker',
|
||||
category: 'script',
|
||||
roles: ['script', 'display'],
|
||||
weights: ONE,
|
||||
italics: [],
|
||||
letterEm: 0.516,
|
||||
},
|
||||
// ── mono ──
|
||||
mono: {
|
||||
name: 'DM Mono',
|
||||
stack: '"DM Mono", ui-monospace, monospace',
|
||||
pkg: 'dm-mono',
|
||||
category: 'mono',
|
||||
roles: ['mono'],
|
||||
weights: [300, 400, 500],
|
||||
italics: [],
|
||||
letterEm: 0.613,
|
||||
},
|
||||
} as const satisfies Record<string, BookFontFace>;
|
||||
|
||||
export type BookFontFamily = keyof typeof BOOK_FONT_FACES;
|
||||
|
||||
/**
|
||||
* The families a book may be set in, as the schema takes them. The first
|
||||
* seven keep the order they shipped in; new ones follow.
|
||||
*/
|
||||
export const BOOK_FONTS_IDS = [
|
||||
'sans',
|
||||
'inter',
|
||||
'serif',
|
||||
'garamond',
|
||||
'playfair',
|
||||
'display',
|
||||
'bebas',
|
||||
'montserrat',
|
||||
'fraunces',
|
||||
'cormorant',
|
||||
'dmserif',
|
||||
'bodoni',
|
||||
'anton',
|
||||
'oswald',
|
||||
'league',
|
||||
'shoulders',
|
||||
'archivo',
|
||||
'shrikhand',
|
||||
'alfa',
|
||||
'bricolage',
|
||||
'abril',
|
||||
'caveat',
|
||||
'dancing',
|
||||
'pacifico',
|
||||
'vibes',
|
||||
'sacramento',
|
||||
'kaushan',
|
||||
'allura',
|
||||
'parisienne',
|
||||
'satisfy',
|
||||
'yellowtail',
|
||||
'homemade',
|
||||
'marker',
|
||||
'mono',
|
||||
] as const satisfies readonly BookFontFamily[];
|
||||
|
||||
/**
|
||||
* The faces whose latin files the service worker installs with the app, so a
|
||||
* book set in them opens offline before Studio was ever opened online: the
|
||||
* seven families and weights the book shipped with before this catalogue grew.
|
||||
* Every other face is cached the first time a book that uses it is opened.
|
||||
*/
|
||||
export const BOOK_FONTS_PRECACHED: Readonly<Partial<Record<BookFontFamily, readonly BookWeight[]>>> = {
|
||||
sans: MID,
|
||||
inter: MID,
|
||||
serif: MID,
|
||||
garamond: MID,
|
||||
playfair: MID,
|
||||
display: MID,
|
||||
bebas: ONE,
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
import {
|
||||
bookFacesUsed,
|
||||
bookFontCountsInMix,
|
||||
bookFontFace,
|
||||
bookFontHasItalic,
|
||||
bookFontHasWeight,
|
||||
bookFontIndex,
|
||||
bookFontRole,
|
||||
bookFontSuits,
|
||||
bookFontsUsed,
|
||||
bookWeightWord,
|
||||
isBookFontId,
|
||||
nearestBookWeight,
|
||||
withRealWeight,
|
||||
} from './book-fonts';
|
||||
import { BOOK_FONT_FACES, BOOK_FONTS_IDS, BOOK_FONTS_PRECACHED, BOOK_WEIGHTS } from './book-fonts.data';
|
||||
import { bookFontIdSchema, bookWeightSchema } from './book-op-enums';
|
||||
import { applyBookOps } from './book-ops';
|
||||
import { blankBook } from './book-pages';
|
||||
import {
|
||||
bookStatsElementSchema,
|
||||
bookTextElementSchema,
|
||||
type BookDocument,
|
||||
type BookElement,
|
||||
type BookSpread,
|
||||
} from './book.schema';
|
||||
import { describeSpread } from './describe-spread';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
// BKFONT-001 to BKFONT-010
|
||||
|
||||
/**
|
||||
* The book's typefaces as one catalogue: the schema's ids, the weights and
|
||||
* italics each family really ships, what each is for, and the snap the
|
||||
* Studio and the page operations share, so the assistant never sets a weight
|
||||
* the editor would not.
|
||||
*/
|
||||
|
||||
const frame = { x: 230, y: 40, w: 120, h: 30 };
|
||||
const text = (id: string, over: Record<string, unknown> = {}) =>
|
||||
bookTextElementSchema.parse({ id, kind: 'text', frame, text: 'Lisbon', ...over }) as BookElement;
|
||||
|
||||
function book(elements: BookElement[]): BookDocument {
|
||||
const doc = blankBook({ title: 'Lisbon', newId: (p) => `${p}-x` });
|
||||
const ids = ['sp-cover', 'sp-first', 'sp-in', 'sp-last', 'sp-back'];
|
||||
const spreads = doc.spreads.map((s, i) => ({ ...s, id: ids[i] as string }));
|
||||
const inner = { ...(spreads[2] as BookSpread), role: 'inner' as const, elements };
|
||||
return { ...doc, spreads: [spreads[0], spreads[1], inner, spreads[3], spreads[4]] as BookSpread[] };
|
||||
}
|
||||
|
||||
let seq = 0;
|
||||
const run = (doc: BookDocument, ops: Parameters<typeof applyBookOps>[1]['ops']) =>
|
||||
applyBookOps(doc, { spreadId: 'sp-in', ops }, { newId: (p) => `${p}-n${++seq}` });
|
||||
const inner = (doc: BookDocument) => doc.spreads[2] as BookSpread;
|
||||
|
||||
describe('the catalogue', () => {
|
||||
it('BKFONT-001: names every family the schema takes, once, with a face, a stack ending in a generic and a weight', () => {
|
||||
expect([...BOOK_FONTS_IDS].sort()).toEqual(Object.keys(BOOK_FONT_FACES).sort());
|
||||
expect(new Set(BOOK_FONTS_IDS).size).toBe(BOOK_FONTS_IDS.length);
|
||||
expect(BOOK_FONTS_IDS.slice(0, 7)).toEqual(['sans', 'inter', 'serif', 'garamond', 'playfair', 'display', 'bebas']);
|
||||
for (const id of BOOK_FONTS_IDS) {
|
||||
const face = BOOK_FONT_FACES[id];
|
||||
expect(face.stack.startsWith(`"${face.name}"`), id).toBe(true);
|
||||
expect(['sans-serif', 'serif', 'cursive', 'monospace'], id).toContain(face.stack.split(',').pop()?.trim());
|
||||
expect(face.weights.length, id).toBeGreaterThan(0);
|
||||
expect(
|
||||
[...face.weights].sort((a, b) => a - b),
|
||||
id,
|
||||
).toEqual([...face.weights]);
|
||||
for (const w of face.italics) expect(face.weights, `${id} italic ${w}`).toContain(w);
|
||||
expect(face.letterEm, id).toBeGreaterThan(0.2);
|
||||
expect(face.letterEm, id).toBeLessThan(0.7);
|
||||
expect(face.roles.length, id).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('BKFONT-002: has real italics for the serifs that used to be slanted by the browser', () => {
|
||||
for (const id of ['serif', 'garamond', 'playfair', 'cormorant']) {
|
||||
expect(bookFontHasItalic(id, 400), id).toBe(true);
|
||||
}
|
||||
expect(bookFontHasItalic('sans', 400)).toBe(false);
|
||||
expect(bookFontHasItalic('playfair', 300)).toBe(false);
|
||||
expect(bookFontHasItalic('nothing', 400)).toBe(false);
|
||||
});
|
||||
|
||||
it('BKFONT-003: installs offline only the faces the book shipped with, each a weight the family has', () => {
|
||||
for (const [id, weights] of Object.entries(BOOK_FONTS_PRECACHED)) {
|
||||
for (const w of weights ?? []) expect(bookFontHasWeight(id, w), `${id} ${w}`).toBe(true);
|
||||
}
|
||||
expect(Object.keys(BOOK_FONTS_PRECACHED).sort()).toEqual(BOOK_FONTS_IDS.slice(0, 7).sort());
|
||||
});
|
||||
|
||||
it('BKFONT-004: reads a family and its roles, and answers an unknown id without guessing', () => {
|
||||
expect(bookFontFace('anton')?.name).toBe('Anton');
|
||||
expect(bookFontFace('toString')).toBeUndefined();
|
||||
expect([isBookFontId('caveat'), isBookFontId('comic')]).toEqual([true, false]);
|
||||
expect([bookFontRole('caveat'), bookFontRole('mono'), bookFontRole('anton'), bookFontRole('nothing')]).toEqual([
|
||||
'script',
|
||||
'mono',
|
||||
'display',
|
||||
'body',
|
||||
]);
|
||||
expect([bookFontSuits('garamond', 'body'), bookFontSuits('anton', 'body')]).toEqual([true, false]);
|
||||
expect([bookFontCountsInMix('caveat'), bookFontCountsInMix('mono'), bookFontCountsInMix('serif')]).toEqual([
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('weights', () => {
|
||||
it('BKFONT-005: snaps to the nearest weight a family has, the lighter on a tie, and leaves an unknown family alone', () => {
|
||||
expect(nearestBookWeight('anton', 900)).toBe(400);
|
||||
expect(nearestBookWeight('oswald', 100)).toBe(200);
|
||||
expect(nearestBookWeight('oswald', 900)).toBe(700);
|
||||
expect(nearestBookWeight('mono', 700)).toBe(500);
|
||||
expect(nearestBookWeight('cormorant', 200)).toBe(300);
|
||||
expect(nearestBookWeight('sans', 900)).toBe(900);
|
||||
expect(nearestBookWeight('unknown', 600)).toBe(600);
|
||||
expect([bookFontHasWeight('bebas', 700), bookFontHasWeight('unknown', 650)]).toEqual([false, true]);
|
||||
expect(BOOK_WEIGHTS.map(bookWeightWord)).toEqual([
|
||||
'thin',
|
||||
'extralight',
|
||||
'light',
|
||||
'regular',
|
||||
'medium',
|
||||
'semibold',
|
||||
'bold',
|
||||
'extrabold',
|
||||
'black',
|
||||
]);
|
||||
expect(bookWeightWord(450)).toBe('450');
|
||||
});
|
||||
|
||||
it('BKFONT-006: moves a typeset element onto a real weight and hands every other element back as it was', () => {
|
||||
const heavy = text('t', { font: 'anton', weight: 700 });
|
||||
expect(withRealWeight(heavy)).toMatchObject({ weight: 400 });
|
||||
const fine = text('t', { font: 'sans', weight: 700 });
|
||||
expect(withRealWeight(fine)).toBe(fine);
|
||||
const stats = bookStatsElementSchema.parse({ id: 's', kind: 'stats', frame, font: 'league' }) as BookElement;
|
||||
expect(withRealWeight(stats)).toMatchObject({ weight: 400 });
|
||||
const shape = { id: 'x', kind: 'shape', frame } as unknown as BookElement;
|
||||
expect(withRealWeight(shape)).toBe(shape);
|
||||
});
|
||||
|
||||
it('BKFONT-007: takes every weight from 100 to 900 in a document and nothing between them', () => {
|
||||
expect(bookTextElementSchema.safeParse({ id: 't', kind: 'text', frame, weight: 100 }).success).toBe(true);
|
||||
expect(bookTextElementSchema.safeParse({ id: 't', kind: 'text', frame, weight: 900 }).success).toBe(true);
|
||||
expect(bookTextElementSchema.safeParse({ id: 't', kind: 'text', frame, weight: 450 }).success).toBe(false);
|
||||
expect(bookWeightSchema.safeParse(800).success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a book and its faces', () => {
|
||||
it('BKFONT-008: lists the families a book is set in and the faces it needs drawn, each once', () => {
|
||||
const doc = book([
|
||||
text('t1', { font: 'caveat', weight: 700 }),
|
||||
text('t2', { font: 'serif', italic: true }),
|
||||
text('t3', { font: 'sans', italic: true }),
|
||||
text('t4', { font: 'anton', text: ' ' }),
|
||||
bookStatsElementSchema.parse({ id: 's', kind: 'stats', frame, font: 'oswald' }) as BookElement,
|
||||
]);
|
||||
const used = bookFontsUsed(doc);
|
||||
expect(used).toEqual(expect.arrayContaining(['caveat', 'serif', 'oswald']));
|
||||
expect(used).not.toContain('anton');
|
||||
expect(used.indexOf('serif')).toBeLessThan(used.indexOf('caveat'));
|
||||
const faces = bookFacesUsed(doc);
|
||||
expect(faces).toContainEqual({ font: 'caveat', weight: 700, italic: false });
|
||||
expect(faces).toContainEqual({ font: 'serif', weight: 400, italic: true });
|
||||
// Poppins has no italic file: its slant is the browser's, so the upright face is what loads.
|
||||
expect(faces).toContainEqual({ font: 'sans', weight: 400, italic: false });
|
||||
expect(faces).toContainEqual({ font: 'oswald', weight: 700, italic: false });
|
||||
expect(faces).toContainEqual({ font: 'oswald', weight: 600, italic: false });
|
||||
const keys = faces.map((f) => `${f.font}|${f.weight}|${f.italic}`);
|
||||
expect(new Set(keys).size).toBe(keys.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the assistant reads the same type', () => {
|
||||
it('BKFONT-009: describes every family with its role and weights in the id the tools take', () => {
|
||||
const index = bookFontIndex();
|
||||
for (const id of BOOK_FONTS_IDS) expect(index, id).toContain(id);
|
||||
expect(index).toMatch(/^display: /);
|
||||
expect(index).toContain('script: caveat 400-700');
|
||||
expect(index).toContain('bebas 400 caps');
|
||||
expect(index).toContain('serif Lora 400-700 i');
|
||||
expect(index).not.toMatch(/[\u2013\u2014\n]/);
|
||||
expect(String(bookFontIdSchema.description)).toContain(index);
|
||||
});
|
||||
|
||||
it('BKFONT-010: sets a style or a text change at a weight the family has, and says so in the page description', () => {
|
||||
const doc = book([text('t1')]);
|
||||
const styled = run(doc, [{ op: 'style', elementId: 't1', set: { kind: 'text', font: 'anton', weight: 900 } }]);
|
||||
if (!styled.ok) throw new Error(styled.error.message);
|
||||
expect(inner(styled.doc).elements[0]).toMatchObject({ font: 'anton', weight: 400 });
|
||||
const retexted = run(doc, [{ op: 'text', elementId: 't1', font: 'mono', weight: 700 }]);
|
||||
if (!retexted.ok) throw new Error(retexted.error.message);
|
||||
expect(inner(retexted.doc).elements[0]).toMatchObject({ font: 'mono', weight: 500 });
|
||||
const page = describeSpread(retexted.doc, 'sp-in')?.text ?? '';
|
||||
expect(page).toContain('mono (mono) 11 pt medium');
|
||||
const light = run(doc, [{ op: 'style', elementId: 't1', set: { kind: 'text', font: 'fraunces', weight: 300 } }]);
|
||||
if (!light.ok) throw new Error(light.error.message);
|
||||
expect(describeSpread(light.doc, 'sp-in')?.text).toContain('fraunces (display) 11 pt light');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,188 @@
|
||||
import {
|
||||
BOOK_FONT_CATEGORIES,
|
||||
BOOK_FONT_FACES,
|
||||
BOOK_FONT_ROLES,
|
||||
BOOK_FONTS_IDS,
|
||||
BOOK_FONTS_PRECACHED,
|
||||
BOOK_WEIGHTS,
|
||||
type BookFontCategory,
|
||||
type BookFontFace,
|
||||
type BookFontFamily,
|
||||
type BookFontRole,
|
||||
type BookWeight,
|
||||
} from './book-fonts.data';
|
||||
import type { BookDocument, BookElement } from './book.schema';
|
||||
|
||||
/**
|
||||
* What the book's typefaces can do, read off the catalogue in
|
||||
* book-fonts.data.ts: which weights and italics a family really has, what it
|
||||
* is for, and which faces a book uses. Shared by the Studio, the findings,
|
||||
* the design engine and the page operations, so the assistant and the editor
|
||||
* snap a weight the same way.
|
||||
*/
|
||||
|
||||
export {
|
||||
BOOK_FONT_CATEGORIES,
|
||||
BOOK_FONT_FACES,
|
||||
BOOK_FONT_ROLES,
|
||||
BOOK_FONTS_PRECACHED,
|
||||
BOOK_WEIGHTS,
|
||||
type BookFontCategory,
|
||||
type BookFontFace,
|
||||
type BookFontRole,
|
||||
type BookWeight,
|
||||
};
|
||||
|
||||
/** The family behind an id, or undefined for an id this build does not know. */
|
||||
export function bookFontFace(id: string): BookFontFace | undefined {
|
||||
return Object.prototype.hasOwnProperty.call(BOOK_FONT_FACES, id) ? BOOK_FONT_FACES[id as BookFontFamily] : undefined;
|
||||
}
|
||||
|
||||
/** Whether `id` is a family of the catalogue. */
|
||||
export const isBookFontId = (id: string): id is BookFontFamily => bookFontFace(id) !== undefined;
|
||||
|
||||
/** Whether a family ships the weight. An unknown family is not second-guessed. */
|
||||
export function bookFontHasWeight(id: string, weight: number): boolean {
|
||||
const face = bookFontFace(id);
|
||||
return face ? (face.weights as readonly number[]).includes(weight) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The nearest weight a family really has, the lighter one on a tie.
|
||||
*
|
||||
* A synthesised bold is a smeared regular in print, so a family with one
|
||||
* weight gets that weight rather than a request the renderer has to fake. An
|
||||
* unknown family keeps what was asked.
|
||||
*/
|
||||
export function nearestBookWeight(id: string, weight: number): number {
|
||||
const face = bookFontFace(id);
|
||||
if (!face || (face.weights as readonly number[]).includes(weight)) return weight;
|
||||
return face.weights.reduce<number>(
|
||||
(best, w) => (Math.abs(w - weight) < Math.abs(best - weight) ? w : best),
|
||||
face.weights[0] ?? weight,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The lighter weight a travel element sets beside its figures (a unit after
|
||||
* a number, a caption under it): a step under the weight, never under 400
|
||||
* unless the weight itself is, and one the family has.
|
||||
*/
|
||||
export function bookLighterWeight(id: string, weight: number): number {
|
||||
return nearestBookWeight(id, Math.min(weight, Math.max(400, weight - 100)));
|
||||
}
|
||||
|
||||
/** Whether a family has a real italic at that weight, rather than the browser's slant. */
|
||||
export function bookFontHasItalic(id: string, weight: number): boolean {
|
||||
const face = bookFontFace(id);
|
||||
return !!face && (face.italics as readonly number[]).includes(weight);
|
||||
}
|
||||
|
||||
/** What a family is for: the first of its roles, `body` for an unknown one. */
|
||||
export const bookFontRole = (id: string): BookFontRole => bookFontFace(id)?.roles[0] ?? 'body';
|
||||
|
||||
/** Whether a family suits a role at all, not only as its first. */
|
||||
export function bookFontSuits(id: string, role: BookFontRole): boolean {
|
||||
return bookFontFace(id)?.roles.includes(role) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a family counts towards the faces a book is set in. A script laid
|
||||
* over a title and a mono line on a ticket are ornaments, not a third voice.
|
||||
*/
|
||||
export function bookFontCountsInMix(id: string): boolean {
|
||||
const role = bookFontRole(id);
|
||||
return role !== 'script' && role !== 'mono';
|
||||
}
|
||||
|
||||
/** The element with its weight moved onto one its family has, when it is set in type at all. */
|
||||
export function withRealWeight<T extends BookElement>(el: T): T {
|
||||
if (!('font' in el) || !('weight' in el)) return el;
|
||||
const weight = nearestBookWeight(el.font, el.weight);
|
||||
return weight === el.weight ? el : { ...el, weight: weight as BookWeight };
|
||||
}
|
||||
|
||||
/** One face a book is set in: the family, the weight the renderer draws and the slant. */
|
||||
export interface BookFaceUse {
|
||||
font: BookFontFamily;
|
||||
weight: number;
|
||||
italic: boolean;
|
||||
}
|
||||
|
||||
/** The type-setting elements on a book's pages: everything that names a font, an empty text aside. */
|
||||
function typeset(doc: BookDocument): Extract<BookElement, { font: string }>[] {
|
||||
const out: Extract<BookElement, { font: string }>[] = [];
|
||||
for (const spread of doc.spreads) {
|
||||
for (const el of spread.elements) {
|
||||
if ('font' in el && (el.kind !== 'text' || el.text.trim())) out.push(el);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The families a book is set in, page numbers included when shown, in catalogue order. */
|
||||
export function bookFontsUsed(doc: BookDocument): BookFontFamily[] {
|
||||
const used = new Set<string>(typeset(doc).map((el) => el.font));
|
||||
if (doc.page.pageNumbers.show) used.add(doc.page.pageNumbers.font);
|
||||
return BOOK_FONTS_IDS.filter((id) => used.has(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Every face a book needs drawn, each once: what the Studio warms into the
|
||||
* offline cache when a book opens, so the book still reads in its own type
|
||||
* without a connection. Travel elements set a lighter weight beside their
|
||||
* figures, which is why both of those are named.
|
||||
*/
|
||||
export function bookFacesUsed(doc: BookDocument): BookFaceUse[] {
|
||||
const seen = new Map<string, BookFaceUse>();
|
||||
const add = (font: BookFontFamily, weight: number, italic: boolean) => {
|
||||
const use = { font, weight: nearestBookWeight(font, weight), italic: italic && bookFontHasItalic(font, weight) };
|
||||
seen.set(`${use.font}|${use.weight}|${use.italic}`, use);
|
||||
};
|
||||
for (const el of typeset(doc)) {
|
||||
add(el.font, el.weight, el.kind === 'text' && el.italic);
|
||||
if (el.kind !== 'text') add(el.font, bookLighterWeight(el.font, el.weight), false);
|
||||
}
|
||||
if (doc.page.pageNumbers.show) add(doc.page.pageNumbers.font, 500, false);
|
||||
return [...seen.values()];
|
||||
}
|
||||
|
||||
/** A weight as the assistant reads it, the CSS name beside the number. */
|
||||
const WEIGHT_WORDS: Record<BookWeight, string> = {
|
||||
100: 'thin',
|
||||
200: 'extralight',
|
||||
300: 'light',
|
||||
400: 'regular',
|
||||
500: 'medium',
|
||||
600: 'semibold',
|
||||
700: 'bold',
|
||||
800: 'extrabold',
|
||||
900: 'black',
|
||||
};
|
||||
|
||||
export const bookWeightWord = (weight: number): string => WEIGHT_WORDS[weight as BookWeight] ?? String(weight);
|
||||
|
||||
/**
|
||||
* The catalogue as one line per role, for the font id's description in the
|
||||
* tool schemas: an id, its face and the weights it has, an i for a real
|
||||
* italic, so a model picks a script for an overlay and never asks Anton for
|
||||
* bold.
|
||||
*/
|
||||
export function bookFontIndex(): string {
|
||||
const span = (ws: readonly number[]) => (ws.length === 1 ? `${ws[0]}` : `${ws[0]}-${ws[ws.length - 1]}`);
|
||||
return BOOK_FONT_ROLES.map((role) => {
|
||||
const ids = BOOK_FONTS_IDS.filter((id) => bookFontRole(id) === role);
|
||||
if (!ids.length) return '';
|
||||
const listed = ids.map((id) => {
|
||||
const face = BOOK_FONT_FACES[id];
|
||||
const italic = face.italics.length ? ' i' : '';
|
||||
const caps = 'caps' in face ? ' caps' : '';
|
||||
// The face is named only where the id does not already say it, for the size of the tool schemas.
|
||||
const named = face.name.toLowerCase().startsWith(id) ? '' : ` ${face.name}`;
|
||||
return `${id}${named} ${span(face.weights)}${italic}${caps}`;
|
||||
});
|
||||
return `${role}: ${listed.join(', ')}.`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bookFontIndex } from './book-fonts';
|
||||
import { BOOK_ICON_NAMES } from './book-icons';
|
||||
import {
|
||||
BOOK_BADGES,
|
||||
@@ -37,14 +38,20 @@ const text = bookTextElementSchema.shape;
|
||||
const shape = bookShapeElementSchema.shape;
|
||||
const map = bookMapElementSchema.shape;
|
||||
|
||||
/** The book's typefaces, with the face each id sets. */
|
||||
/**
|
||||
* The book's typefaces by what they are for, each with its face, the weights
|
||||
* it has and an i where it has a real italic. A weight a family lacks is set
|
||||
* at its nearest one, as in the Studio.
|
||||
*/
|
||||
export const bookFontIdSchema = z
|
||||
.enum(BOOK_FONTS_IDS)
|
||||
.describe(
|
||||
'Typeface: sans Poppins, inter Inter, serif Lora, garamond EB Garamond, playfair Playfair Display, display MuseoModerno, bebas Bebas Neue (caps, one weight).',
|
||||
);
|
||||
.describe(`Typeface by role; weights a family lacks snap to its nearest. ${bookFontIndex()}`);
|
||||
/** The typeface of a travel element, where the full line above would repeat ten times. */
|
||||
export const bookTypesetFontSchema = z.enum(BOOK_FONTS_IDS).describe('Typeface id, as for text.');
|
||||
/** How heavy type is set, 100 thin to 900 black. */
|
||||
export const bookWeightSchema = bookTextElementSchema.shape.weight
|
||||
.unwrap()
|
||||
.describe('Weight 100 thin to 900 black, 400 regular, 700 bold; snapped to one the family has.');
|
||||
|
||||
/** Every shape the Studio draws, and so every mask a photo can be cut to. */
|
||||
export const bookShapeIdSchema = z
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { relayoutSpread } from './auto/auto-book';
|
||||
import type { AutoEntry, AutoInput } from './auto/auto-types';
|
||||
import { bookElementPropsSchema, type BOOK_ELEMENT_OP_SCHEMAS, type BookNewElement } from './book-element-ops.schema';
|
||||
import { withRealWeight } from './book-fonts';
|
||||
import { bookGridElements, defaultBookGridBox, findBookGrid } from './book-grids';
|
||||
import {
|
||||
bookBoundWords,
|
||||
@@ -138,7 +139,11 @@ function lookKeys(shape: object): string[] {
|
||||
return Object.keys(shape).filter((k) => k !== 'kind');
|
||||
}
|
||||
|
||||
/** The element with the given look fields set; millimetre fields rounded as the document stores them. */
|
||||
/**
|
||||
* The element with the given look fields set; millimetre fields rounded as the
|
||||
* document stores them, and the weight moved onto one its family has, as the
|
||||
* Studio's weight control does.
|
||||
*/
|
||||
function withProps(el: BookElement, props: Record<string, unknown>): BookElement {
|
||||
const keys = PROP_KEYS.get(el.kind) ?? [];
|
||||
const next: Record<string, unknown> = { ...el };
|
||||
@@ -147,7 +152,7 @@ function withProps(el: BookElement, props: Record<string, unknown>): BookElement
|
||||
if (value === undefined) continue;
|
||||
next[key] = (key === 'radius' || key === 'strokeWidth') && typeof value === 'number' ? round2(value) : value;
|
||||
}
|
||||
return next as unknown as BookElement;
|
||||
return withRealWeight(next as unknown as BookElement);
|
||||
}
|
||||
|
||||
// ── add ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withRealWeight } from './book-fonts';
|
||||
import { bookGridElements, defaultBookGridBox, findBookGrid, type BookGridDef } from './book-grids';
|
||||
import { bookElementId } from './book-ids';
|
||||
import { BOOK_TEXT_STYLES, applyBookTemplate, classifyBookTexts, findBookTemplate } from './book-layouts';
|
||||
@@ -413,9 +414,11 @@ function opText(spread: BookSpread, op: Op<'text'>, env: Env): BookSpread {
|
||||
op.elementId !== undefined
|
||||
? textEl(spread, op.elementId, env)
|
||||
: classifyBookTexts(roleTexts(spread))[op.role ?? 'body'];
|
||||
if (existing && existing.kind === 'text') return replaceEl(spread, retext(unlocked(existing, env), op, env));
|
||||
// The weight lands on one the family has, whichever of the two a change names.
|
||||
if (existing && existing.kind === 'text')
|
||||
return replaceEl(spread, withRealWeight(retext(unlocked(existing, env), op, env)));
|
||||
withRoom(spread, 1, env);
|
||||
return { ...spread, elements: [...spread.elements, newText(spread, op, env)] };
|
||||
return { ...spread, elements: [...spread.elements, withRealWeight(newText(spread, op, env))] };
|
||||
}
|
||||
|
||||
function retext(el: BookTextElement, op: Op<'text'>, env: Env): BookTextElement {
|
||||
|
||||
@@ -21,6 +21,9 @@ import { describe, expect, it } from 'vitest';
|
||||
|
||||
const frame = { x: 12, y: 18, w: 60, h: 40 };
|
||||
|
||||
/** A revision one past what this build reads. */
|
||||
const NEWER = BOOK_SCHEMA_REV + 1;
|
||||
|
||||
const doc = (over: Record<string, unknown> = {}) => ({
|
||||
version: 1,
|
||||
title: 'Lisbon',
|
||||
@@ -40,7 +43,8 @@ const doc = (over: Record<string, unknown> = {}) => ({
|
||||
|
||||
describe('the revision a document was written by', () => {
|
||||
it('BKREV-001: reads 1 for every document from before the field, and for anything that is not a whole number', () => {
|
||||
expect(BOOK_SCHEMA_REV).toBe(1);
|
||||
// Raised for the first time by the wider type: new families and weights an older reader drops.
|
||||
expect(BOOK_SCHEMA_REV).toBeGreaterThanOrEqual(2);
|
||||
expect(bookSchemaRev(doc())).toBe(1);
|
||||
expect(bookSchemaRev(null)).toBe(1);
|
||||
expect(bookSchemaRev('{"schemaRev":4}')).toBe(1);
|
||||
@@ -56,33 +60,34 @@ describe('the revision a document was written by', () => {
|
||||
expect(odd.success && odd.data.spreads[0]?.elements).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('BKREV-003: leaves a book saved today as it was, with no revision written into it', () => {
|
||||
it('BKREV-003: a writer at revision 1 leaves a book as it was, with no revision written into it', () => {
|
||||
const today = normalizeBookDocument(doc());
|
||||
expect('schemaRev' in today).toBe(false);
|
||||
expect(stampBookSchemaRev(today)).toBe(today);
|
||||
expect(JSON.stringify(stampBookSchemaRev(today))).toBe(JSON.stringify(today));
|
||||
expect(stampBookSchemaRev(today, 1)).toBe(today);
|
||||
expect(JSON.stringify(stampBookSchemaRev(today, 1))).toBe(JSON.stringify(today));
|
||||
expect(stampBookSchemaRev(today).schemaRev).toBe(BOOK_SCHEMA_REV);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a reader older than the document', () => {
|
||||
it('BKREV-004: knows the document is newer than it, read from the raw value even when the rest will not parse', () => {
|
||||
expect(isBookNewerThanReader(doc({ schemaRev: 2 }))).toBe(true);
|
||||
expect(isBookNewerThanReader(doc({ schemaRev: NEWER }))).toBe(true);
|
||||
expect(isBookNewerThanReader(doc({ schemaRev: 1 }))).toBe(false);
|
||||
expect(isBookNewerThanReader(doc())).toBe(false);
|
||||
// A reader of a later revision reads the same document as its own.
|
||||
expect(isBookNewerThanReader(doc({ schemaRev: 2 }), 2)).toBe(false);
|
||||
const unreadable = doc({ schemaRev: 5, page: 'not a page' });
|
||||
expect(isBookNewerThanReader(doc({ schemaRev: NEWER }), NEWER)).toBe(false);
|
||||
const unreadable = doc({ schemaRev: NEWER + 3, page: 'not a page' });
|
||||
expect(isBookNewerThanReader(unreadable)).toBe(true);
|
||||
});
|
||||
|
||||
it('BKREV-005: reads a newer book as far as it can and says so, so nothing it dropped is saved', () => {
|
||||
const sticker = { id: 'x1', kind: 'sticker', frame, stickerId: 'lemon' };
|
||||
const raw = doc({ schemaRev: 2 });
|
||||
const raw = doc({ schemaRev: NEWER });
|
||||
const spread = (raw.spreads as { elements: unknown[] }[])[0];
|
||||
if (spread) spread.elements.push(sticker);
|
||||
const read = readBookDocument(raw);
|
||||
expect(read.newer).toBe(true);
|
||||
expect(read.document.schemaRev).toBe(2);
|
||||
expect(read.document.schemaRev).toBe(NEWER);
|
||||
expect(read.document.spreads[0]?.elements.map((e) => e.id)).toEqual(['t1']);
|
||||
expect(readBookDocument(doc()).newer).toBe(false);
|
||||
});
|
||||
@@ -99,10 +104,10 @@ describe('a reader older than the document', () => {
|
||||
describe('a save request carrying a newer book', () => {
|
||||
it('BKREV-007: reaches the server as its revision alone, so the save is refused there rather than at the pipe', () => {
|
||||
const sticker = { id: 'x1', kind: 'sticker', frame, stickerId: 'lemon' };
|
||||
const raw = doc({ schemaRev: 2 });
|
||||
const raw = doc({ schemaRev: NEWER });
|
||||
const spread = (raw.spreads as { elements: unknown[] }[])[0];
|
||||
if (spread) spread.elements.push(sticker);
|
||||
expect(bookSaveRequestSchema.parse({ title: 'T', document: raw }).document).toEqual({ schemaRev: 2 });
|
||||
expect(bookSaveRequestSchema.parse({ title: 'T', document: raw }).document).toEqual({ schemaRev: NEWER });
|
||||
// A book of today still reads whole, revision or none.
|
||||
const today = bookSaveRequestSchema.parse({ title: 'T', document: doc() }).document;
|
||||
expect(today).toEqual(bookDocumentSchema.parse(doc()));
|
||||
|
||||
@@ -28,7 +28,7 @@ import type { BookDocument } from './book.schema';
|
||||
* saves only once that is above 1, so a book saved today stores exactly what
|
||||
* it stored before this existed.
|
||||
*/
|
||||
export const BOOK_SCHEMA_REV = 1;
|
||||
export const BOOK_SCHEMA_REV = 2;
|
||||
|
||||
/** The `code` a refused save carries in its body when the stored book is newer than the writer. */
|
||||
export const BOOK_SCHEMA_NEWER_CODE = 'book_schema_newer';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BOOK_FONTS_IDS, BOOK_WEIGHTS, type BookFontFamily } from './book-fonts.data';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
@@ -157,15 +159,15 @@ export const BOOK_SHAPES = [
|
||||
export type BookShapeId = (typeof BOOK_SHAPES)[number];
|
||||
|
||||
/**
|
||||
* The families a book may be set in.
|
||||
* The families a book may be set in, and the weights type is set at. The
|
||||
* catalogue is book-fonts.data.ts: which face each id names, its weights, its
|
||||
* real italics and what it is for.
|
||||
*
|
||||
* `sans`, `serif` and `display` are the three the format started with and are
|
||||
* kept by name, so every document written so far still parses — they now point
|
||||
* at bundled faces rather than at whatever the rendering machine happened to
|
||||
* have installed. See client/src/components/Studio/bookFonts.ts.
|
||||
* kept by name, so every document written so far still parses.
|
||||
*/
|
||||
export const BOOK_FONTS_IDS = ['sans', 'inter', 'serif', 'garamond', 'playfair', 'display', 'bebas'] as const;
|
||||
export type BookFontFamily = (typeof BOOK_FONTS_IDS)[number];
|
||||
export { BOOK_FONTS_IDS, type BookFontFamily };
|
||||
const bookWeightSchema = z.literal(BOOK_WEIGHTS);
|
||||
|
||||
export const bookFrameSchema = z.object({
|
||||
/** Millimetres from the left edge of the spread. Negative means bleed. */
|
||||
@@ -230,7 +232,7 @@ export const bookTextElementSchema = z.object({
|
||||
font: z.enum(BOOK_FONTS_IDS).default('sans'),
|
||||
/** Points. */
|
||||
size: z.number().min(4).max(200).default(11),
|
||||
weight: z.union([z.literal(400), z.literal(500), z.literal(600), z.literal(700)]).default(400),
|
||||
weight: bookWeightSchema.default(400),
|
||||
italic: z.boolean().default(false),
|
||||
align: z.enum(['left', 'center', 'right', 'justify']).default('left'),
|
||||
/** Multiple of the font size. */
|
||||
@@ -362,7 +364,7 @@ const typeset = {
|
||||
* together, the way `textScale` moves their size. Without it the only way to
|
||||
* lighten a stats block was to not use one.
|
||||
*/
|
||||
weight: z.union([z.literal(400), z.literal(500), z.literal(600), z.literal(700)]).default(700),
|
||||
weight: bookWeightSchema.default(700),
|
||||
/** Set when the journey has moved on since these values were taken. */
|
||||
stale: z.boolean().default(false),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { photoDpi, spreadFindings } from './book-findings';
|
||||
import { designFindings, sortBookFindings } from './book-findings-design';
|
||||
import { bookFontRole, bookWeightWord, nearestBookWeight } from './book-fonts';
|
||||
import { BOOK_GRIDS } from './book-grids';
|
||||
import { BOOK_COVER_TEMPLATES, BOOK_TEMPLATES, type BookTemplate } from './book-layouts';
|
||||
import type { BookPhotoInfo } from './book-ops-core';
|
||||
@@ -85,7 +86,9 @@ function elementWords(el: BookElement, photos: readonly BookPhotoInfo[] | undefi
|
||||
photos?.find((p) => p.photoId === el.photoId),
|
||||
);
|
||||
case 'text': {
|
||||
const style = `${el.font} ${n(el.size)} pt${el.weight >= 600 ? ' bold' : ''}${el.italic ? ' italic' : ''}, ${el.align}, ${el.color}`;
|
||||
const weight = nearestBookWeight(el.font, el.weight);
|
||||
const set = `${el.font} (${bookFontRole(el.font)}) ${n(el.size)} pt${weight === 400 ? '' : ` ${bookWeightWord(weight)}`}`;
|
||||
const style = `${set}${el.italic ? ' italic' : ''}, ${el.align}, ${el.color}`;
|
||||
const bound = el.binding ? `, bound to ${el.binding.source}${el.overridden ? ' (overridden)' : ''}` : '';
|
||||
return `${style}: "${head(el.text, 80)}"${bound}`;
|
||||
}
|
||||
@@ -110,7 +113,7 @@ type TravelElement = Extract<BookElement, { kind: 'map' | 'stats' | 'countries'
|
||||
|
||||
/** What a travel element shows, and how it is set: its face, ink, accent and size. */
|
||||
function travelWords(el: TravelElement): string {
|
||||
const set = `${el.font} ${el.weight >= 600 ? 'bold' : 'regular'}, ink ${el.color}, accent ${el.accent}, type x${n(el.textScale)}`;
|
||||
const set = `${el.font} ${bookWeightWord(nearestBookWeight(el.font, el.weight))}, ink ${el.color}, accent ${el.accent}, type x${n(el.textScale)}`;
|
||||
const stale = el.stale ? '; its figures are out of date with the journey' : '';
|
||||
return `${travelWhat(el)}; ${set}${stale}`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { buildBook, distributeGallery } from '../auto/auto-book';
|
||||
import type { AutoEntry, AutoInput, AutoPhoto } from '../auto/auto-types';
|
||||
import { bookLevelFindings, sortBookFindings } from '../book-findings-design';
|
||||
import { withRealWeight } from '../book-fonts';
|
||||
import { bookElementId } from '../book-ids';
|
||||
import type { BookPhotoInfo } from '../book-ops-core';
|
||||
import type { BookFinding } from '../book-ops.schema';
|
||||
@@ -252,7 +253,9 @@ export function designBook(
|
||||
const repairCtx = { page, photos: input.photos, look, newId };
|
||||
const designed = [...framing, ...inner.spreads, ...closing].map((d): DesignedSpread => {
|
||||
const fixed = repairSpread(d.spread, repairCtx);
|
||||
return { ...d, spread: fixed.spread, findings: fixed.findings };
|
||||
// Every face at a weight its family has: a travel element's bold in a one-weight poster face would be faked.
|
||||
const spread = { ...fixed.spread, elements: fixed.spread.elements.map(withRealWeight) };
|
||||
return { ...d, spread, findings: fixed.findings };
|
||||
});
|
||||
|
||||
const merged = mergeDesign(doc, designed, directives.keep);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AutoEntry, AutoPhoto } from '../auto/auto-types';
|
||||
import { DESIGN_TEXT_HEADROOM, MIN_PRINT_DPI, textHeightNeeded } from '../book-findings';
|
||||
import { nearestBookWeight } from '../book-fonts';
|
||||
import type { BookPhotoInfo } from '../book-ops-core';
|
||||
import { formatBookDate } from '../book-text';
|
||||
import { bookBadgeValue, bookTravelBadge, type BookTravelContext } from '../book-travel';
|
||||
@@ -158,6 +159,7 @@ type TextAt = Pick<BookFrame, 'x' | 'y' | 'w'>;
|
||||
/** A text in the book's look, its frame as tall as its words need. */
|
||||
export function textEl(c: ComposeCtx, at: TextAt, words: string, role: TextRole, opts: TextOpts = {}): BookTextElement {
|
||||
const defaultColour = role === 'caption' || role === 'meta' ? c.look.muted : c.look.ink;
|
||||
const font = role === 'heading' || role === 'sub' ? c.look.heading : c.look.body;
|
||||
const el: BookTextElement = {
|
||||
id: c.newId('t'),
|
||||
kind: 'text',
|
||||
@@ -166,9 +168,10 @@ export function textEl(c: ComposeCtx, at: TextAt, words: string, role: TextRole,
|
||||
opacity: 1,
|
||||
locked: false,
|
||||
text: words,
|
||||
font: role === 'heading' || role === 'sub' ? c.look.heading : c.look.body,
|
||||
font,
|
||||
size: opts.size ?? c.look.sizes[role],
|
||||
weight: WEIGHT[role],
|
||||
// A family that lacks the role's weight is set at its nearest, never at a synthesised one.
|
||||
weight: nearestBookWeight(font, WEIGHT[role]) as BookTextElement['weight'],
|
||||
italic: false,
|
||||
align: opts.align ?? 'left',
|
||||
leading: LEADING[role],
|
||||
@@ -258,7 +261,7 @@ export function titleBlock(
|
||||
if (value) {
|
||||
const chip = frameOf(box.x, y, scaled(c, 24), scaled(c, 7));
|
||||
const badge = bookTravelBadge({ id: c.newId('bd'), frame: chip, variant: 'day', value });
|
||||
els.push({ ...badge, font: c.look.heading, accent: c.look.accent });
|
||||
els.push({ ...badge, font: c.look.figures, accent: c.look.accent });
|
||||
y += chip.h + 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ export function composeSummary(
|
||||
const textSide = mapSide === 'left' ? 'right' : 'left';
|
||||
const els: BookElement[] = [];
|
||||
const map = bookTravelMap(c.travel, stats, { id: c.newId('mp'), frame: liveBox(c, mapSide), scope: 'journey' });
|
||||
els.push({ ...map, style: c.look.mapStyle, font: c.look.heading, color: c.look.ink, accent: c.look.accent });
|
||||
els.push({ ...map, style: c.look.mapStyle, font: c.look.figures, color: c.look.ink, accent: c.look.accent });
|
||||
const head = label(c, words, textSide);
|
||||
els.push(...head.els);
|
||||
const metrics = metricsOf(c, stats);
|
||||
@@ -169,7 +169,7 @@ export function composeSummary(
|
||||
const box = liveBox(c, textSide);
|
||||
const frame = frameOf(box.x, head.bottom, box.w, Math.min(box.h * 0.6, box.y + box.h - head.bottom));
|
||||
const figures = bookTravelStats(stats, { id: c.newId('st'), frame, metrics });
|
||||
els.push({ ...figures, font: c.look.heading, color: c.look.ink, accent: c.look.accent });
|
||||
els.push({ ...figures, font: c.look.figures, color: c.look.ink, accent: c.look.accent });
|
||||
}
|
||||
return els;
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export function composeCountries(c: ComposeCtx, words: string): BookElement[] {
|
||||
const head = label(c, words, 'left');
|
||||
const box = liveBox(c, 'right');
|
||||
const countries = bookTravelCountries(c.travel, { id: c.newId('co'), frame: box });
|
||||
return [...head.els, { ...countries, font: c.look.heading, color: c.look.ink, accent: c.look.accent }];
|
||||
return [...head.els, { ...countries, font: c.look.figures, color: c.look.ink, accent: c.look.accent }];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -146,7 +146,7 @@ function listUnder(c: ComposeCtx, entry: AutoEntry, foot: Foot): BookElement | n
|
||||
if (foot.top + h > foot.bottom) return null;
|
||||
const labels = { pros: c.travel.labels.pros, cons: c.travel.labels.cons };
|
||||
const list = bookTravelList(labels, { id: c.newId('li'), frame: frameOf(foot.x, foot.top, foot.w, h), items });
|
||||
return { ...list, font: c.look.heading, color: c.look.ink, accent: c.look.accent };
|
||||
return { ...list, font: c.look.figures, color: c.look.ink, accent: c.look.accent };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +293,7 @@ function chapterMap(c: ComposeCtx, entry: AutoEntry, box: BookFrame): BookElemen
|
||||
const h = box.h * 0.3;
|
||||
const frame = frameOf(box.x, box.y + box.h - h, box.w, h);
|
||||
const map = bookTravelMap(c.travel, stats, { id: c.newId('mp'), frame, scope: { tripId } });
|
||||
const look = { style: c.look.mapStyle, font: c.look.heading, color: c.look.ink, accent: c.look.accent };
|
||||
const look = { style: c.look.mapStyle, font: c.look.figures, color: c.look.ink, accent: c.look.accent };
|
||||
return { ...map, ...look, pinStyle: 'dot' };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bookFontHasWeight } from '../book-fonts';
|
||||
import { bookPageSetupSchema, type BookDocument, type BookTextElement } from '../book.schema';
|
||||
import { designBook, type BookDesignInput } from './design-book';
|
||||
import { bookDesignDirectivesSchema, type BookDesignDirectivesInput } from './design-directives.schema';
|
||||
@@ -7,7 +8,7 @@ import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
// BOOKSTY-001 to BOOKSTY-004
|
||||
// BOOKSTY-001 to BOOKSTY-007
|
||||
|
||||
/**
|
||||
* The type scale of a designed book: `type.scale` reaches a poster's sizes,
|
||||
@@ -78,3 +79,29 @@ describe('the cover title', () => {
|
||||
expect((big?.frame.y ?? 0) + (big?.frame.h ?? 0)).toBeLessThanOrEqual(page.pageHeight);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the typefaces by their roles', () => {
|
||||
it('BOOKSTY-005: keeps a body face that sets running text, and falls back to the style for one that does not', () => {
|
||||
expect(lookOf({ style: 'classic', type: { body: 'garamond' } }).body).toBe('garamond');
|
||||
// A poster face and a script are a title's, never a story's.
|
||||
expect(lookOf({ style: 'classic', type: { body: 'anton' } }).body).toBe('sans');
|
||||
expect(lookOf({ style: 'editorial', type: { body: 'caveat' } }).body).toBe('garamond');
|
||||
});
|
||||
|
||||
it('BOOKSTY-006: sets the travel elements in the heading face, unless that is a script or a mono face', () => {
|
||||
expect(lookOf({ style: 'bold' }).figures).toBe('bebas');
|
||||
const script = lookOf({ style: 'journal', type: { heading: 'caveat' } });
|
||||
expect([script.heading, script.figures]).toEqual(['caveat', 'garamond']);
|
||||
expect(lookOf({ style: 'classic', type: { heading: 'mono', body: 'inter' } }).figures).toBe('inter');
|
||||
});
|
||||
|
||||
it('BOOKSTY-007: sets every text and travel element at a weight its family really has', () => {
|
||||
const { doc } = designBook(null, rich(), { style: 'bold', type: { heading: 'anton' } });
|
||||
const typeset = doc.spreads.flatMap((s) => s.elements.filter((e) => 'weight' in e && 'font' in e));
|
||||
expect(typeset.length).toBeGreaterThan(0);
|
||||
for (const el of typeset) {
|
||||
if ('weight' in el && 'font' in el) expect(bookFontHasWeight(el.font, el.weight), el.id).toBe(true);
|
||||
}
|
||||
expect(typeset.some((e) => 'font' in e && e.font === 'anton')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { accentFromPalettes, contrastRatio, parseHex, readPalette, readableInk, toHex } from '../book-colour';
|
||||
import { bookFontCountsInMix, bookFontSuits } from '../book-fonts';
|
||||
import type { BookPhotoInfo } from '../book-ops-core';
|
||||
import type { BookFontFamily, BookPageSetup, BookPhotoElement } from '../book.schema';
|
||||
import type { BookDesignDirectives, BookDesignStyle } from './design-directives.schema';
|
||||
@@ -7,8 +8,12 @@ import type { BookDesignDirectives, BookDesignStyle } from './design-directives.
|
||||
* The look of a designed book: its paper, ink, accent, the two typefaces,
|
||||
* the type sizes and the page grid, from the style and the directives.
|
||||
*
|
||||
* At most two families per book; badges, figures, maps and lists take the
|
||||
* heading face. Sizes are set for a 210 mm page and scale with the page
|
||||
* At most two families per book, read by their roles (book-fonts.data.ts):
|
||||
* the body face has to be one that sets running text, so a poster face or a
|
||||
* script asked for as the body falls back to the style's own. Badges,
|
||||
* figures, maps and lists take the heading face, unless that is a script or
|
||||
* a mono face, which sets a title well and a column of figures badly; then
|
||||
* they take the body face. Sizes are set for a 210 mm page and scale with the page
|
||||
* width, times `type.scale`, never under what a reader can read and never
|
||||
* over what a text element takes. The cover title has a tier of its own,
|
||||
* `display`, the heading size times `type.display`: 1 by default, so a book
|
||||
@@ -31,6 +36,8 @@ export interface BookDesignLook {
|
||||
onScrim: string;
|
||||
heading: BookFontFamily;
|
||||
body: BookFontFamily;
|
||||
/** The face of the travel elements: figures, marks, maps and lists. */
|
||||
figures: BookFontFamily;
|
||||
/** Points. `display` is the cover title's, the heading's unless `type.display` raises it. */
|
||||
sizes: { display: number; heading: number; sub: number; body: number; caption: number; meta: number };
|
||||
/** The frame decoration and the slight turn a scrapbook gives its pictures. */
|
||||
@@ -172,6 +179,8 @@ export function designLook(
|
||||
const size = (role: keyof typeof BASE_SIZES, times = 1) =>
|
||||
Math.min(MAX_PT, Math.max(FLOOR[role], round1(BASE_SIZES[role] * scale * times)));
|
||||
const heading = directives.type?.heading ?? preset.heading;
|
||||
const asked = directives.type?.body;
|
||||
const body = asked && bookFontSuits(asked, 'body') ? asked : preset.body;
|
||||
return {
|
||||
style: directives.style,
|
||||
paper: colours.paper,
|
||||
@@ -182,7 +191,8 @@ export function designLook(
|
||||
scrimOpacity: 0.6,
|
||||
onScrim: '#ffffff',
|
||||
heading,
|
||||
body: directives.type?.body ?? preset.body,
|
||||
body,
|
||||
figures: bookFontCountsInMix(heading) ? heading : body,
|
||||
sizes: {
|
||||
display: size('heading', directives.type?.display ?? 1),
|
||||
heading: size('heading'),
|
||||
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -613,7 +613,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'فسيفساء',
|
||||
'journey.studio.tpl.bandQuote': 'كلمات في الوسط',
|
||||
'journey.studio.tpl.staggerFour': 'أربع متدرجة',
|
||||
'journey.studio.weightMissing': 'هذا الخط لا يوفر هذا الوزن',
|
||||
'journey.studio.mapSource': 'مصدر الخريطة',
|
||||
'journey.studio.mapSourceVector': 'المخططات',
|
||||
'journey.studio.mapSourceRelief': 'تضاريس',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'تغيير الخط',
|
||||
'journey.studio.fonts.search': 'البحث عن خطوط',
|
||||
'journey.studio.fonts.list': 'الخطوط',
|
||||
'journey.studio.fonts.used': 'مستخدمة في هذا الكتاب',
|
||||
'journey.studio.fonts.none': 'لا يوجد خط مطابق',
|
||||
'journey.studio.fonts.range': 'من {from} إلى {to}',
|
||||
'journey.studio.fonts.cat.sans': 'بدون تذييل',
|
||||
'journey.studio.fonts.cat.serif': 'مذيّلة',
|
||||
'journey.studio.fonts.cat.elegant': 'عناوين أنيقة',
|
||||
'journey.studio.fonts.cat.condensed': 'ضيقة وملصقات',
|
||||
'journey.studio.fonts.cat.retro': 'ثقيلة وريترو',
|
||||
'journey.studio.fonts.cat.script': 'خط يد وزخرفي',
|
||||
'journey.studio.fonts.cat.mono': 'ثابتة العرض',
|
||||
'journey.studio.weight.100': 'رفيع',
|
||||
'journey.studio.weight.200': 'خفيف جدًا',
|
||||
'journey.studio.weight.300': 'خفيف',
|
||||
'journey.studio.weight.400': 'عادي',
|
||||
'journey.studio.weight.500': 'متوسط',
|
||||
'journey.studio.weight.600': 'شبه عريض',
|
||||
'journey.studio.weight.700': 'عريض',
|
||||
'journey.studio.weight.800': 'عريض جدًا',
|
||||
'journey.studio.weight.900': 'أسود',
|
||||
'journey.studio.weightOnly': 'وزن واحد فقط',
|
||||
'journey.studio.italicSlanted': 'مائل بالإمالة: لا يملك هذا الخط مائلًا خاصًا به',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -641,8 +641,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.bandQuote': 'Arada sitat',
|
||||
'journey.studio.tpl.staggerFour': 'Pilləli dördlük',
|
||||
|
||||
'journey.studio.weightMissing': 'Bu şriftdə həmin qalınlıq mövcud deyil',
|
||||
|
||||
'journey.studio.mapSource': 'Xəritə mənbəyi',
|
||||
'journey.studio.mapSourceVector': 'Konturlar',
|
||||
'journey.studio.mapSourceRelief': 'Relyef',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Şrifti dəyiş',
|
||||
'journey.studio.fonts.search': 'Şrift axtar',
|
||||
'journey.studio.fonts.list': 'Şriftlər',
|
||||
'journey.studio.fonts.used': 'Bu kitabda istifadə olunanlar',
|
||||
'journey.studio.fonts.none': 'Uyğun şrift yoxdur',
|
||||
'journey.studio.fonts.range': '{from} ilə {to} arası',
|
||||
'journey.studio.fonts.cat.sans': 'Dırnaqsız',
|
||||
'journey.studio.fonts.cat.serif': 'Dırnaqlı',
|
||||
'journey.studio.fonts.cat.elegant': 'Zərif başlıq',
|
||||
'journey.studio.fonts.cat.condensed': 'Dar və afişa',
|
||||
'journey.studio.fonts.cat.retro': 'Qalın və retro',
|
||||
'journey.studio.fonts.cat.script': 'Yazı və əl yazısı',
|
||||
'journey.studio.fonts.cat.mono': 'Sabit enli',
|
||||
'journey.studio.weight.100': 'Nazik',
|
||||
'journey.studio.weight.200': 'Çox yüngül',
|
||||
'journey.studio.weight.300': 'Yüngül',
|
||||
'journey.studio.weight.400': 'Adi',
|
||||
'journey.studio.weight.500': 'Orta',
|
||||
'journey.studio.weight.600': 'Yarımqalın',
|
||||
'journey.studio.weight.700': 'Qalın',
|
||||
'journey.studio.weight.800': 'Çox qalın',
|
||||
'journey.studio.weight.900': 'Qara',
|
||||
'journey.studio.weightOnly': 'Yalnız bir qalınlıq',
|
||||
'journey.studio.italicSlanted': 'Kursiv, maili: bu şriftin öz kursivi yoxdur',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -587,7 +587,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaico',
|
||||
'journey.studio.tpl.bandQuote': 'Palavras no meio',
|
||||
'journey.studio.tpl.staggerFour': 'Quatro escalonadas',
|
||||
'journey.studio.weightMissing': 'Esta fonte não tem esse peso',
|
||||
'journey.studio.mapSource': 'Origem do mapa',
|
||||
'journey.studio.mapSourceVector': 'Contornos',
|
||||
'journey.studio.mapSourceRelief': 'Relevo',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Trocar fonte',
|
||||
'journey.studio.fonts.search': 'Buscar fontes',
|
||||
'journey.studio.fonts.list': 'Fontes',
|
||||
'journey.studio.fonts.used': 'Usadas neste livro',
|
||||
'journey.studio.fonts.none': 'Nenhuma fonte corresponde',
|
||||
'journey.studio.fonts.range': '{from} a {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Sem serifa',
|
||||
'journey.studio.fonts.cat.serif': 'Com serifa',
|
||||
'journey.studio.fonts.cat.elegant': 'Títulos elegantes',
|
||||
'journey.studio.fonts.cat.condensed': 'Estreita e cartaz',
|
||||
'journey.studio.fonts.cat.retro': 'Pesada e retrô',
|
||||
'journey.studio.fonts.cat.script': 'Caligráfica e manuscrita',
|
||||
'journey.studio.fonts.cat.mono': 'Monoespaçada',
|
||||
'journey.studio.weight.100': 'Ultrafino',
|
||||
'journey.studio.weight.200': 'Extrafino',
|
||||
'journey.studio.weight.300': 'Fino',
|
||||
'journey.studio.weight.400': 'Normal',
|
||||
'journey.studio.weight.500': 'Médio',
|
||||
'journey.studio.weight.600': 'Seminegrito',
|
||||
'journey.studio.weight.700': 'Negrito',
|
||||
'journey.studio.weight.800': 'Extranegrito',
|
||||
'journey.studio.weight.900': 'Preto',
|
||||
'journey.studio.weightOnly': 'Apenas um peso',
|
||||
'journey.studio.italicSlanted': 'Itálico, inclinado: esta fonte não tem itálico próprio',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -115,6 +116,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -587,7 +587,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaic',
|
||||
'journey.studio.tpl.bandQuote': 'Paraules al mig',
|
||||
'journey.studio.tpl.staggerFour': 'Quatre esglaonades',
|
||||
'journey.studio.weightMissing': 'Aquesta tipografia no té aquest gruix',
|
||||
'journey.studio.mapSource': 'Origen del mapa',
|
||||
'journey.studio.mapSourceVector': 'Contorns',
|
||||
'journey.studio.mapSourceRelief': 'Relleu',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Canvia la tipografia',
|
||||
'journey.studio.fonts.search': 'Cerca tipografies',
|
||||
'journey.studio.fonts.list': 'Tipografies',
|
||||
'journey.studio.fonts.used': 'Utilitzades en aquest llibre',
|
||||
'journey.studio.fonts.none': 'Cap tipografia coincideix',
|
||||
'journey.studio.fonts.range': '{from} a {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Sense serifa',
|
||||
'journey.studio.fonts.cat.serif': 'Amb serifa',
|
||||
'journey.studio.fonts.cat.elegant': 'Títols elegants',
|
||||
'journey.studio.fonts.cat.condensed': 'Estreta i cartell',
|
||||
'journey.studio.fonts.cat.retro': 'Gruixuda i retro',
|
||||
'journey.studio.fonts.cat.script': 'Cal·ligràfica i manuscrita',
|
||||
'journey.studio.fonts.cat.mono': 'Monoespaiada',
|
||||
'journey.studio.weight.100': 'Ultrafina',
|
||||
'journey.studio.weight.200': 'Extrafina',
|
||||
'journey.studio.weight.300': 'Fina',
|
||||
'journey.studio.weight.400': 'Normal',
|
||||
'journey.studio.weight.500': 'Mitjana',
|
||||
'journey.studio.weight.600': 'Seminegreta',
|
||||
'journey.studio.weight.700': 'Negreta',
|
||||
'journey.studio.weight.800': 'Extranegreta',
|
||||
'journey.studio.weight.900': 'Negra',
|
||||
'journey.studio.weightOnly': 'Només un pes',
|
||||
'journey.studio.italicSlanted': 'Cursiva, inclinada: aquesta tipografia no té cursiva pròpia',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -591,7 +591,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mozaika',
|
||||
'journey.studio.tpl.bandQuote': 'Slova uprostřed',
|
||||
'journey.studio.tpl.staggerFour': 'Čtyři přesazené',
|
||||
'journey.studio.weightMissing': 'Toto písmo tento řez nemá',
|
||||
'journey.studio.mapSource': 'Zdroj mapy',
|
||||
'journey.studio.mapSourceVector': 'Obrysy',
|
||||
'journey.studio.mapSourceRelief': 'Reliéf',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Změnit písmo',
|
||||
'journey.studio.fonts.search': 'Hledat písma',
|
||||
'journey.studio.fonts.list': 'Písma',
|
||||
'journey.studio.fonts.used': 'Použitá v této knize',
|
||||
'journey.studio.fonts.none': 'Žádné písmo neodpovídá',
|
||||
'journey.studio.fonts.range': '{from} až {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Bezpatková',
|
||||
'journey.studio.fonts.cat.serif': 'Patková',
|
||||
'journey.studio.fonts.cat.elegant': 'Elegantní titulková',
|
||||
'journey.studio.fonts.cat.condensed': 'Úzká a plakátová',
|
||||
'journey.studio.fonts.cat.retro': 'Těžká a retro',
|
||||
'journey.studio.fonts.cat.script': 'Psací a ručně psaná',
|
||||
'journey.studio.fonts.cat.mono': 'Neproporcionální',
|
||||
'journey.studio.weight.100': 'Tenké',
|
||||
'journey.studio.weight.200': 'Extra lehké',
|
||||
'journey.studio.weight.300': 'Lehké',
|
||||
'journey.studio.weight.400': 'Běžné',
|
||||
'journey.studio.weight.500': 'Střední',
|
||||
'journey.studio.weight.600': 'Polotučné',
|
||||
'journey.studio.weight.700': 'Tučné',
|
||||
'journey.studio.weight.800': 'Extra tučné',
|
||||
'journey.studio.weight.900': 'Černé',
|
||||
'journey.studio.weightOnly': 'Jen jeden řez',
|
||||
'journey.studio.italicSlanted': 'Kurzíva, skloněná: toto písmo nemá vlastní kurzívu',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -588,7 +588,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaik',
|
||||
'journey.studio.tpl.bandQuote': 'Worte dazwischen',
|
||||
'journey.studio.tpl.staggerFour': 'Vier versetzt',
|
||||
'journey.studio.weightMissing': 'Diese Schrift hat diesen Schnitt nicht',
|
||||
'journey.studio.mapSource': 'Kartenquelle',
|
||||
'journey.studio.mapSourceVector': 'Umrisse',
|
||||
'journey.studio.mapSourceRelief': 'Relief',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Schrift ändern',
|
||||
'journey.studio.fonts.search': 'Schriften suchen',
|
||||
'journey.studio.fonts.list': 'Schriften',
|
||||
'journey.studio.fonts.used': 'In diesem Buch verwendet',
|
||||
'journey.studio.fonts.none': 'Keine Schrift passt',
|
||||
'journey.studio.fonts.range': '{from} bis {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Serifenlos',
|
||||
'journey.studio.fonts.cat.serif': 'Serifen',
|
||||
'journey.studio.fonts.cat.elegant': 'Elegante Titelschriften',
|
||||
'journey.studio.fonts.cat.condensed': 'Schmal und Plakat',
|
||||
'journey.studio.fonts.cat.retro': 'Kräftig und Retro',
|
||||
'journey.studio.fonts.cat.script': 'Schreib- und Handschrift',
|
||||
'journey.studio.fonts.cat.mono': 'Festbreite',
|
||||
'journey.studio.weight.100': 'Dünn',
|
||||
'journey.studio.weight.200': 'Extraleicht',
|
||||
'journey.studio.weight.300': 'Leicht',
|
||||
'journey.studio.weight.400': 'Normal',
|
||||
'journey.studio.weight.500': 'Mittel',
|
||||
'journey.studio.weight.600': 'Halbfett',
|
||||
'journey.studio.weight.700': 'Fett',
|
||||
'journey.studio.weight.800': 'Extrafett',
|
||||
'journey.studio.weight.900': 'Schwarz',
|
||||
'journey.studio.weightOnly': 'Nur ein Schnitt',
|
||||
'journey.studio.italicSlanted': 'Kursiv, geneigt: diese Schrift hat keine eigene Kursive',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -584,7 +584,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaic',
|
||||
'journey.studio.tpl.bandQuote': 'Words between',
|
||||
'journey.studio.tpl.staggerFour': 'Staggered four',
|
||||
'journey.studio.weightMissing': 'This typeface does not ship that weight',
|
||||
'journey.studio.mapSource': 'Map source',
|
||||
'journey.studio.mapSourceVector': 'Outlines',
|
||||
'journey.studio.mapSourceRelief': 'Relief',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Change typeface',
|
||||
'journey.studio.fonts.search': 'Search typefaces',
|
||||
'journey.studio.fonts.list': 'Typefaces',
|
||||
'journey.studio.fonts.used': 'Used in this book',
|
||||
'journey.studio.fonts.none': 'No typeface matches',
|
||||
'journey.studio.fonts.range': '{from} to {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Sans serif',
|
||||
'journey.studio.fonts.cat.serif': 'Serif',
|
||||
'journey.studio.fonts.cat.elegant': 'Elegant display',
|
||||
'journey.studio.fonts.cat.condensed': 'Condensed and poster',
|
||||
'journey.studio.fonts.cat.retro': 'Heavy and retro',
|
||||
'journey.studio.fonts.cat.script': 'Script and handwriting',
|
||||
'journey.studio.fonts.cat.mono': 'Monospace',
|
||||
'journey.studio.weight.100': 'Thin',
|
||||
'journey.studio.weight.200': 'Extra light',
|
||||
'journey.studio.weight.300': 'Light',
|
||||
'journey.studio.weight.400': 'Regular',
|
||||
'journey.studio.weight.500': 'Medium',
|
||||
'journey.studio.weight.600': 'Semibold',
|
||||
'journey.studio.weight.700': 'Bold',
|
||||
'journey.studio.weight.800': 'Extra bold',
|
||||
'journey.studio.weight.900': 'Black',
|
||||
'journey.studio.weightOnly': 'One weight only',
|
||||
'journey.studio.italicSlanted': 'Italic, slanted: this typeface has no italic of its own',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -587,7 +587,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaico',
|
||||
'journey.studio.tpl.bandQuote': 'Palabras en medio',
|
||||
'journey.studio.tpl.staggerFour': 'Cuatro escalonadas',
|
||||
'journey.studio.weightMissing': 'Esta tipografía no tiene ese grosor',
|
||||
'journey.studio.mapSource': 'Origen del mapa',
|
||||
'journey.studio.mapSourceVector': 'Contornos',
|
||||
'journey.studio.mapSourceRelief': 'Relieve',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Cambiar tipografía',
|
||||
'journey.studio.fonts.search': 'Buscar tipografías',
|
||||
'journey.studio.fonts.list': 'Tipografías',
|
||||
'journey.studio.fonts.used': 'Usadas en este libro',
|
||||
'journey.studio.fonts.none': 'Ninguna tipografía coincide',
|
||||
'journey.studio.fonts.range': '{from} a {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Sin serifa',
|
||||
'journey.studio.fonts.cat.serif': 'Con serifa',
|
||||
'journey.studio.fonts.cat.elegant': 'Títulos elegantes',
|
||||
'journey.studio.fonts.cat.condensed': 'Estrecha y cartel',
|
||||
'journey.studio.fonts.cat.retro': 'Gruesa y retro',
|
||||
'journey.studio.fonts.cat.script': 'Caligráfica y manuscrita',
|
||||
'journey.studio.fonts.cat.mono': 'Monoespaciada',
|
||||
'journey.studio.weight.100': 'Ultrafina',
|
||||
'journey.studio.weight.200': 'Extrafina',
|
||||
'journey.studio.weight.300': 'Fina',
|
||||
'journey.studio.weight.400': 'Normal',
|
||||
'journey.studio.weight.500': 'Media',
|
||||
'journey.studio.weight.600': 'Seminegrita',
|
||||
'journey.studio.weight.700': 'Negrita',
|
||||
'journey.studio.weight.800': 'Extranegrita',
|
||||
'journey.studio.weight.900': 'Negra',
|
||||
'journey.studio.weightOnly': 'Un solo peso',
|
||||
'journey.studio.italicSlanted': 'Cursiva, inclinada: esta tipografía no tiene cursiva propia',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -585,7 +585,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaiik',
|
||||
'journey.studio.tpl.bandQuote': 'Sõnad vahel',
|
||||
'journey.studio.tpl.staggerFour': 'Neli nihkega',
|
||||
'journey.studio.weightMissing': 'Sellel kirjatüübil pole seda paksust',
|
||||
'journey.studio.mapSource': 'Kaardi allikas',
|
||||
'journey.studio.mapSourceVector': 'Kontuurid',
|
||||
'journey.studio.mapSourceRelief': 'Reljeef',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Muuda kirjatüüpi',
|
||||
'journey.studio.fonts.search': 'Otsi kirjatüüpe',
|
||||
'journey.studio.fonts.list': 'Kirjatüübid',
|
||||
'journey.studio.fonts.used': 'Selles raamatus kasutatud',
|
||||
'journey.studio.fonts.none': 'Ükski kirjatüüp ei sobi',
|
||||
'journey.studio.fonts.range': '{from} kuni {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Seriifideta',
|
||||
'journey.studio.fonts.cat.serif': 'Seriifidega',
|
||||
'journey.studio.fonts.cat.elegant': 'Elegantsed pealkirjakirjad',
|
||||
'journey.studio.fonts.cat.condensed': 'Kitsas ja plakat',
|
||||
'journey.studio.fonts.cat.retro': 'Raske ja retro',
|
||||
'journey.studio.fonts.cat.script': 'Kalligraafiline ja käekiri',
|
||||
'journey.studio.fonts.cat.mono': 'Püsilaiusega',
|
||||
'journey.studio.weight.100': 'Õhuke',
|
||||
'journey.studio.weight.200': 'Eriti kerge',
|
||||
'journey.studio.weight.300': 'Kerge',
|
||||
'journey.studio.weight.400': 'Tavaline',
|
||||
'journey.studio.weight.500': 'Keskmine',
|
||||
'journey.studio.weight.600': 'Poolpaks',
|
||||
'journey.studio.weight.700': 'Paks',
|
||||
'journey.studio.weight.800': 'Eriti paks',
|
||||
'journey.studio.weight.900': 'Must',
|
||||
'journey.studio.weightOnly': 'Ainult üks paksus',
|
||||
'journey.studio.italicSlanted': 'Kursiiv, kallutatud: sellel kirjatüübil pole oma kursiivi',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -586,7 +586,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaïque',
|
||||
'journey.studio.tpl.bandQuote': 'Mots au milieu',
|
||||
'journey.studio.tpl.staggerFour': 'Quatre décalées',
|
||||
'journey.studio.weightMissing': 'Cette police n’a pas cette graisse',
|
||||
'journey.studio.mapSource': 'Source de la carte',
|
||||
'journey.studio.mapSourceVector': 'Contours',
|
||||
'journey.studio.mapSourceRelief': 'Relief',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Changer de police',
|
||||
'journey.studio.fonts.search': 'Rechercher une police',
|
||||
'journey.studio.fonts.list': 'Polices',
|
||||
'journey.studio.fonts.used': 'Utilisées dans ce livre',
|
||||
'journey.studio.fonts.none': 'Aucune police ne correspond',
|
||||
'journey.studio.fonts.range': '{from} à {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Sans empattements',
|
||||
'journey.studio.fonts.cat.serif': 'Avec empattements',
|
||||
'journey.studio.fonts.cat.elegant': 'Titrage élégant',
|
||||
'journey.studio.fonts.cat.condensed': 'Étroite et affiche',
|
||||
'journey.studio.fonts.cat.retro': 'Grasse et rétro',
|
||||
'journey.studio.fonts.cat.script': 'Script et manuscrite',
|
||||
'journey.studio.fonts.cat.mono': 'Chasse fixe',
|
||||
'journey.studio.weight.100': 'Ultra-fin',
|
||||
'journey.studio.weight.200': 'Extra-fin',
|
||||
'journey.studio.weight.300': 'Fin',
|
||||
'journey.studio.weight.400': 'Normal',
|
||||
'journey.studio.weight.500': 'Moyen',
|
||||
'journey.studio.weight.600': 'Demi-gras',
|
||||
'journey.studio.weight.700': 'Gras',
|
||||
'journey.studio.weight.800': 'Extra-gras',
|
||||
'journey.studio.weight.900': 'Noir',
|
||||
'journey.studio.weightOnly': 'Une seule graisse',
|
||||
'journey.studio.italicSlanted': "Italique, penché : cette police n'a pas d'italique propre",
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -590,7 +590,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Ψηφιδωτό',
|
||||
'journey.studio.tpl.bandQuote': 'Λέξεις ανάμεσα',
|
||||
'journey.studio.tpl.staggerFour': 'Τέσσερις σε κλιμάκωση',
|
||||
'journey.studio.weightMissing': 'Αυτή η γραμματοσειρά δεν έχει αυτό το βάρος',
|
||||
'journey.studio.mapSource': 'Πηγή χάρτη',
|
||||
'journey.studio.mapSourceVector': 'Περιγράμματα',
|
||||
'journey.studio.mapSourceRelief': 'Ανάγλυφο',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Αλλαγή γραμματοσειράς',
|
||||
'journey.studio.fonts.search': 'Αναζήτηση γραμματοσειρών',
|
||||
'journey.studio.fonts.list': 'Γραμματοσειρές',
|
||||
'journey.studio.fonts.used': 'Χρησιμοποιούνται σε αυτό το βιβλίο',
|
||||
'journey.studio.fonts.none': 'Καμία γραμματοσειρά δεν ταιριάζει',
|
||||
'journey.studio.fonts.range': '{from} έως {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Χωρίς πατούρες',
|
||||
'journey.studio.fonts.cat.serif': 'Με πατούρες',
|
||||
'journey.studio.fonts.cat.elegant': 'Κομψές τίτλων',
|
||||
'journey.studio.fonts.cat.condensed': 'Στενές και αφίσας',
|
||||
'journey.studio.fonts.cat.retro': 'Βαριές και ρετρό',
|
||||
'journey.studio.fonts.cat.script': 'Καλλιγραφικές και χειρόγραφες',
|
||||
'journey.studio.fonts.cat.mono': 'Σταθερού πλάτους',
|
||||
'journey.studio.weight.100': 'Λεπτά',
|
||||
'journey.studio.weight.200': 'Πολύ ελαφριά',
|
||||
'journey.studio.weight.300': 'Ελαφριά',
|
||||
'journey.studio.weight.400': 'Κανονικά',
|
||||
'journey.studio.weight.500': 'Μεσαία',
|
||||
'journey.studio.weight.600': 'Ημίμαυρα',
|
||||
'journey.studio.weight.700': 'Έντονα',
|
||||
'journey.studio.weight.800': 'Πολύ έντονα',
|
||||
'journey.studio.weight.900': 'Μαύρα',
|
||||
'journey.studio.weightOnly': 'Μόνο ένα πάχος',
|
||||
'journey.studio.italicSlanted': 'Πλάγια, με κλίση: αυτή η γραμματοσειρά δεν έχει δικά της πλάγια',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -584,7 +584,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mozaik',
|
||||
'journey.studio.tpl.bandQuote': 'Szavak középen',
|
||||
'journey.studio.tpl.staggerFour': 'Négy eltolva',
|
||||
'journey.studio.weightMissing': 'Ennek a betűtípusnak nincs ilyen vastagsága',
|
||||
'journey.studio.mapSource': 'Térkép forrása',
|
||||
'journey.studio.mapSourceVector': 'Körvonalak',
|
||||
'journey.studio.mapSourceRelief': 'Domborzat',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Betűtípus módosítása',
|
||||
'journey.studio.fonts.search': 'Betűtípusok keresése',
|
||||
'journey.studio.fonts.list': 'Betűtípusok',
|
||||
'journey.studio.fonts.used': 'Ebben a könyvben használt',
|
||||
'journey.studio.fonts.none': 'Nincs egyező betűtípus',
|
||||
'journey.studio.fonts.range': '{from} és {to} között',
|
||||
'journey.studio.fonts.cat.sans': 'Talp nélküli',
|
||||
'journey.studio.fonts.cat.serif': 'Talpas',
|
||||
'journey.studio.fonts.cat.elegant': 'Elegáns címbetűk',
|
||||
'journey.studio.fonts.cat.condensed': 'Keskeny és plakát',
|
||||
'journey.studio.fonts.cat.retro': 'Vaskos és retró',
|
||||
'journey.studio.fonts.cat.script': 'Írott és kézírásos',
|
||||
'journey.studio.fonts.cat.mono': 'Fix szélességű',
|
||||
'journey.studio.weight.100': 'Vékony',
|
||||
'journey.studio.weight.200': 'Extra világos',
|
||||
'journey.studio.weight.300': 'Világos',
|
||||
'journey.studio.weight.400': 'Normál',
|
||||
'journey.studio.weight.500': 'Közepes',
|
||||
'journey.studio.weight.600': 'Félkövér',
|
||||
'journey.studio.weight.700': 'Kövér',
|
||||
'journey.studio.weight.800': 'Extra kövér',
|
||||
'journey.studio.weight.900': 'Fekete',
|
||||
'journey.studio.weightOnly': 'Csak egy vastagság',
|
||||
'journey.studio.italicSlanted': 'Dőlt, döntött: ennek a betűtípusnak nincs saját dőltje',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -576,7 +576,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mozaik',
|
||||
'journey.studio.tpl.bandQuote': 'Kata di tengah',
|
||||
'journey.studio.tpl.staggerFour': 'Empat berselang',
|
||||
'journey.studio.weightMissing': 'Huruf ini tidak punya ketebalan itu',
|
||||
'journey.studio.mapSource': 'Sumber peta',
|
||||
'journey.studio.mapSourceVector': 'Garis luar',
|
||||
'journey.studio.mapSourceRelief': 'Relief',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Ganti jenis huruf',
|
||||
'journey.studio.fonts.search': 'Cari jenis huruf',
|
||||
'journey.studio.fonts.list': 'Jenis huruf',
|
||||
'journey.studio.fonts.used': 'Dipakai di buku ini',
|
||||
'journey.studio.fonts.none': 'Tidak ada jenis huruf yang cocok',
|
||||
'journey.studio.fonts.range': '{from} sampai {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Tanpa kait',
|
||||
'journey.studio.fonts.cat.serif': 'Berkait',
|
||||
'journey.studio.fonts.cat.elegant': 'Judul elegan',
|
||||
'journey.studio.fonts.cat.condensed': 'Ramping dan poster',
|
||||
'journey.studio.fonts.cat.retro': 'Tebal dan retro',
|
||||
'journey.studio.fonts.cat.script': 'Sambung dan tulisan tangan',
|
||||
'journey.studio.fonts.cat.mono': 'Lebar tetap',
|
||||
'journey.studio.weight.100': 'Tipis',
|
||||
'journey.studio.weight.200': 'Sangat ringan',
|
||||
'journey.studio.weight.300': 'Ringan',
|
||||
'journey.studio.weight.400': 'Reguler',
|
||||
'journey.studio.weight.500': 'Sedang',
|
||||
'journey.studio.weight.600': 'Semi tebal',
|
||||
'journey.studio.weight.700': 'Tebal',
|
||||
'journey.studio.weight.800': 'Sangat tebal',
|
||||
'journey.studio.weight.900': 'Hitam',
|
||||
'journey.studio.weightOnly': 'Hanya satu ketebalan',
|
||||
'journey.studio.italicSlanted': 'Miring, dimiringkan: jenis huruf ini tidak punya miring sendiri',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -586,7 +586,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaico',
|
||||
'journey.studio.tpl.bandQuote': 'Parole in mezzo',
|
||||
'journey.studio.tpl.staggerFour': 'Quattro sfalsate',
|
||||
'journey.studio.weightMissing': 'Questo carattere non ha questo peso',
|
||||
'journey.studio.mapSource': 'Origine mappa',
|
||||
'journey.studio.mapSourceVector': 'Contorni',
|
||||
'journey.studio.mapSourceRelief': 'Rilievo',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Cambia carattere',
|
||||
'journey.studio.fonts.search': 'Cerca caratteri',
|
||||
'journey.studio.fonts.list': 'Caratteri',
|
||||
'journey.studio.fonts.used': 'Usati in questo libro',
|
||||
'journey.studio.fonts.none': 'Nessun carattere corrisponde',
|
||||
'journey.studio.fonts.range': 'da {from} a {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Senza grazie',
|
||||
'journey.studio.fonts.cat.serif': 'Con grazie',
|
||||
'journey.studio.fonts.cat.elegant': 'Titoli eleganti',
|
||||
'journey.studio.fonts.cat.condensed': 'Stretto e manifesto',
|
||||
'journey.studio.fonts.cat.retro': 'Pesante e retrò',
|
||||
'journey.studio.fonts.cat.script': 'Calligrafico e manoscritto',
|
||||
'journey.studio.fonts.cat.mono': 'Monospaziato',
|
||||
'journey.studio.weight.100': 'Sottilissimo',
|
||||
'journey.studio.weight.200': 'Extrasottile',
|
||||
'journey.studio.weight.300': 'Sottile',
|
||||
'journey.studio.weight.400': 'Normale',
|
||||
'journey.studio.weight.500': 'Medio',
|
||||
'journey.studio.weight.600': 'Semigrassetto',
|
||||
'journey.studio.weight.700': 'Grassetto',
|
||||
'journey.studio.weight.800': 'Extragrassetto',
|
||||
'journey.studio.weight.900': 'Nero',
|
||||
'journey.studio.weightOnly': 'Un solo peso',
|
||||
'journey.studio.italicSlanted': 'Corsivo, inclinato: questo carattere non ha un corsivo proprio',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -575,7 +575,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'モザイク',
|
||||
'journey.studio.tpl.bandQuote': '間にことば',
|
||||
'journey.studio.tpl.staggerFour': '4枚ずらし',
|
||||
'journey.studio.weightMissing': 'この書体にこのウェイトはありません',
|
||||
'journey.studio.mapSource': '地図のソース',
|
||||
'journey.studio.mapSourceVector': '輪郭',
|
||||
'journey.studio.mapSourceRelief': '陰影起伏',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': '書体を変更',
|
||||
'journey.studio.fonts.search': '書体を検索',
|
||||
'journey.studio.fonts.list': '書体',
|
||||
'journey.studio.fonts.used': 'この本で使用中',
|
||||
'journey.studio.fonts.none': '一致する書体がありません',
|
||||
'journey.studio.fonts.range': '{from}〜{to}',
|
||||
'journey.studio.fonts.cat.sans': 'サンセリフ',
|
||||
'journey.studio.fonts.cat.serif': 'セリフ',
|
||||
'journey.studio.fonts.cat.elegant': 'エレガントな見出し',
|
||||
'journey.studio.fonts.cat.condensed': 'コンデンスとポスター',
|
||||
'journey.studio.fonts.cat.retro': '太字とレトロ',
|
||||
'journey.studio.fonts.cat.script': '筆記体と手書き',
|
||||
'journey.studio.fonts.cat.mono': '等幅',
|
||||
'journey.studio.weight.100': 'シン',
|
||||
'journey.studio.weight.200': 'エクストラライト',
|
||||
'journey.studio.weight.300': 'ライト',
|
||||
'journey.studio.weight.400': 'レギュラー',
|
||||
'journey.studio.weight.500': 'ミディアム',
|
||||
'journey.studio.weight.600': 'セミボールド',
|
||||
'journey.studio.weight.700': 'ボールド',
|
||||
'journey.studio.weight.800': 'エクストラボールド',
|
||||
'journey.studio.weight.900': 'ブラック',
|
||||
'journey.studio.weightOnly': 'ウェイトは1つのみ',
|
||||
'journey.studio.italicSlanted': '斜体(傾斜):この書体には専用のイタリックがありません',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -572,7 +572,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': '모자이크',
|
||||
'journey.studio.tpl.bandQuote': '가운데 글',
|
||||
'journey.studio.tpl.staggerFour': '네 칸 엇갈림',
|
||||
'journey.studio.weightMissing': '이 서체에는 해당 굵기가 없습니다',
|
||||
'journey.studio.mapSource': '지도 소스',
|
||||
'journey.studio.mapSourceVector': '윤곽선',
|
||||
'journey.studio.mapSourceRelief': '음영 지형',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': '서체 변경',
|
||||
'journey.studio.fonts.search': '서체 검색',
|
||||
'journey.studio.fonts.list': '서체',
|
||||
'journey.studio.fonts.used': '이 책에서 사용 중',
|
||||
'journey.studio.fonts.none': '일치하는 서체가 없습니다',
|
||||
'journey.studio.fonts.range': '{from}~{to}',
|
||||
'journey.studio.fonts.cat.sans': '산세리프',
|
||||
'journey.studio.fonts.cat.serif': '세리프',
|
||||
'journey.studio.fonts.cat.elegant': '우아한 제목용',
|
||||
'journey.studio.fonts.cat.condensed': '좁은 폭과 포스터',
|
||||
'journey.studio.fonts.cat.retro': '굵은 레트로',
|
||||
'journey.studio.fonts.cat.script': '필기체와 손글씨',
|
||||
'journey.studio.fonts.cat.mono': '고정폭',
|
||||
'journey.studio.weight.100': '씬',
|
||||
'journey.studio.weight.200': '엑스트라라이트',
|
||||
'journey.studio.weight.300': '라이트',
|
||||
'journey.studio.weight.400': '레귤러',
|
||||
'journey.studio.weight.500': '미디엄',
|
||||
'journey.studio.weight.600': '세미볼드',
|
||||
'journey.studio.weight.700': '볼드',
|
||||
'journey.studio.weight.800': '엑스트라볼드',
|
||||
'journey.studio.weight.900': '블랙',
|
||||
'journey.studio.weightOnly': '굵기 하나만 제공',
|
||||
'journey.studio.italicSlanted': '기울임(경사): 이 서체에는 자체 이탤릭이 없습니다',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -587,7 +587,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mozaïek',
|
||||
'journey.studio.tpl.bandQuote': 'Woorden ertussen',
|
||||
'journey.studio.tpl.staggerFour': 'Vier verspringend',
|
||||
'journey.studio.weightMissing': 'Dit lettertype heeft die dikte niet',
|
||||
'journey.studio.mapSource': 'Kaartbron',
|
||||
'journey.studio.mapSourceVector': 'Omtrekken',
|
||||
'journey.studio.mapSourceRelief': 'Reliëf',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Lettertype wijzigen',
|
||||
'journey.studio.fonts.search': 'Lettertypen zoeken',
|
||||
'journey.studio.fonts.list': 'Lettertypen',
|
||||
'journey.studio.fonts.used': 'Gebruikt in dit boek',
|
||||
'journey.studio.fonts.none': 'Geen lettertype gevonden',
|
||||
'journey.studio.fonts.range': '{from} tot {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Schreefloos',
|
||||
'journey.studio.fonts.cat.serif': 'Schreef',
|
||||
'journey.studio.fonts.cat.elegant': 'Elegante koppen',
|
||||
'journey.studio.fonts.cat.condensed': 'Smal en affiche',
|
||||
'journey.studio.fonts.cat.retro': 'Zwaar en retro',
|
||||
'journey.studio.fonts.cat.script': 'Schrijf- en handschrift',
|
||||
'journey.studio.fonts.cat.mono': 'Vaste breedte',
|
||||
'journey.studio.weight.100': 'Dun',
|
||||
'journey.studio.weight.200': 'Extra licht',
|
||||
'journey.studio.weight.300': 'Licht',
|
||||
'journey.studio.weight.400': 'Normaal',
|
||||
'journey.studio.weight.500': 'Middel',
|
||||
'journey.studio.weight.600': 'Halfvet',
|
||||
'journey.studio.weight.700': 'Vet',
|
||||
'journey.studio.weight.800': 'Extra vet',
|
||||
'journey.studio.weight.900': 'Zwart',
|
||||
'journey.studio.weightOnly': 'Eén gewicht',
|
||||
'journey.studio.italicSlanted': 'Cursief, schuingezet: dit lettertype heeft geen eigen cursief',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -603,7 +603,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mozaika',
|
||||
'journey.studio.tpl.bandQuote': 'Słowa pośrodku',
|
||||
'journey.studio.tpl.staggerFour': 'Cztery przesunięte',
|
||||
'journey.studio.weightMissing': 'Ten krój nie ma tej grubości',
|
||||
'journey.studio.mapSource': 'Źródło mapy',
|
||||
'journey.studio.mapSourceVector': 'Kontury',
|
||||
'journey.studio.mapSourceRelief': 'Relief',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Zmień krój pisma',
|
||||
'journey.studio.fonts.search': 'Szukaj krojów',
|
||||
'journey.studio.fonts.list': 'Kroje pisma',
|
||||
'journey.studio.fonts.used': 'Użyte w tej książce',
|
||||
'journey.studio.fonts.none': 'Żaden krój nie pasuje',
|
||||
'journey.studio.fonts.range': '{from} do {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Bezszeryfowe',
|
||||
'journey.studio.fonts.cat.serif': 'Szeryfowe',
|
||||
'journey.studio.fonts.cat.elegant': 'Eleganckie tytułowe',
|
||||
'journey.studio.fonts.cat.condensed': 'Wąskie i plakatowe',
|
||||
'journey.studio.fonts.cat.retro': 'Ciężkie i retro',
|
||||
'journey.studio.fonts.cat.script': 'Pisane i odręczne',
|
||||
'journey.studio.fonts.cat.mono': 'O stałej szerokości',
|
||||
'journey.studio.weight.100': 'Cienka',
|
||||
'journey.studio.weight.200': 'Bardzo lekka',
|
||||
'journey.studio.weight.300': 'Lekka',
|
||||
'journey.studio.weight.400': 'Zwykła',
|
||||
'journey.studio.weight.500': 'Średnia',
|
||||
'journey.studio.weight.600': 'Półgruba',
|
||||
'journey.studio.weight.700': 'Gruba',
|
||||
'journey.studio.weight.800': 'Bardzo gruba',
|
||||
'journey.studio.weight.900': 'Czarna',
|
||||
'journey.studio.weightOnly': 'Tylko jedna grubość',
|
||||
'journey.studio.italicSlanted': 'Kursywa, pochylona: ten krój nie ma własnej kursywy',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -602,7 +602,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Мозаика',
|
||||
'journey.studio.tpl.bandQuote': 'Слова посередине',
|
||||
'journey.studio.tpl.staggerFour': 'Четыре со сдвигом',
|
||||
'journey.studio.weightMissing': 'У этой гарнитуры нет такого начертания',
|
||||
'journey.studio.mapSource': 'Источник карты',
|
||||
'journey.studio.mapSourceVector': 'Контуры',
|
||||
'journey.studio.mapSourceRelief': 'Рельеф',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Сменить шрифт',
|
||||
'journey.studio.fonts.search': 'Поиск шрифтов',
|
||||
'journey.studio.fonts.list': 'Шрифты',
|
||||
'journey.studio.fonts.used': 'Используются в этой книге',
|
||||
'journey.studio.fonts.none': 'Подходящих шрифтов нет',
|
||||
'journey.studio.fonts.range': 'от {from} до {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Без засечек',
|
||||
'journey.studio.fonts.cat.serif': 'С засечками',
|
||||
'journey.studio.fonts.cat.elegant': 'Изящные заголовочные',
|
||||
'journey.studio.fonts.cat.condensed': 'Узкие и плакатные',
|
||||
'journey.studio.fonts.cat.retro': 'Тяжёлые и ретро',
|
||||
'journey.studio.fonts.cat.script': 'Рукописные и каллиграфические',
|
||||
'journey.studio.fonts.cat.mono': 'Моноширинные',
|
||||
'journey.studio.weight.100': 'Тонкий',
|
||||
'journey.studio.weight.200': 'Сверхсветлый',
|
||||
'journey.studio.weight.300': 'Светлый',
|
||||
'journey.studio.weight.400': 'Обычный',
|
||||
'journey.studio.weight.500': 'Средний',
|
||||
'journey.studio.weight.600': 'Полужирный',
|
||||
'journey.studio.weight.700': 'Жирный',
|
||||
'journey.studio.weight.800': 'Сверхжирный',
|
||||
'journey.studio.weight.900': 'Чёрный',
|
||||
'journey.studio.weightOnly': 'Только одно начертание',
|
||||
'journey.studio.italicSlanted': 'Курсив, наклонённый: у этого шрифта нет собственного курсива',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -596,7 +596,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mozaika',
|
||||
'journey.studio.tpl.bandQuote': 'Slová uprostred',
|
||||
'journey.studio.tpl.staggerFour': 'Štyri presadené',
|
||||
'journey.studio.weightMissing': 'Toto písmo tento rez nemá',
|
||||
'journey.studio.mapSource': 'Zdroj mapy',
|
||||
'journey.studio.mapSourceVector': 'Obrysy',
|
||||
'journey.studio.mapSourceRelief': 'Reliéf',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Zmeniť písmo',
|
||||
'journey.studio.fonts.search': 'Hľadať písma',
|
||||
'journey.studio.fonts.list': 'Písma',
|
||||
'journey.studio.fonts.used': 'Použité v tejto knihe',
|
||||
'journey.studio.fonts.none': 'Žiadne písmo nevyhovuje',
|
||||
'journey.studio.fonts.range': '{from} až {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Bezpätkové',
|
||||
'journey.studio.fonts.cat.serif': 'Pätkové',
|
||||
'journey.studio.fonts.cat.elegant': 'Elegantné titulkové',
|
||||
'journey.studio.fonts.cat.condensed': 'Úzke a plagátové',
|
||||
'journey.studio.fonts.cat.retro': 'Ťažké a retro',
|
||||
'journey.studio.fonts.cat.script': 'Písané a ručne písané',
|
||||
'journey.studio.fonts.cat.mono': 'Neproporcionálne',
|
||||
'journey.studio.weight.100': 'Tenké',
|
||||
'journey.studio.weight.200': 'Extra ľahké',
|
||||
'journey.studio.weight.300': 'Ľahké',
|
||||
'journey.studio.weight.400': 'Bežné',
|
||||
'journey.studio.weight.500': 'Stredné',
|
||||
'journey.studio.weight.600': 'Polotučné',
|
||||
'journey.studio.weight.700': 'Tučné',
|
||||
'journey.studio.weight.800': 'Extra tučné',
|
||||
'journey.studio.weight.900': 'Čierne',
|
||||
'journey.studio.weightOnly': 'Len jeden rez',
|
||||
'journey.studio.italicSlanted': 'Kurzíva, naklonená: toto písmo nemá vlastnú kurzívu',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -584,7 +584,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mosaik',
|
||||
'journey.studio.tpl.bandQuote': 'Ord emellan',
|
||||
'journey.studio.tpl.staggerFour': 'Fyra förskjutna',
|
||||
'journey.studio.weightMissing': 'Det här typsnittet har inte den vikten',
|
||||
'journey.studio.mapSource': 'Kartkälla',
|
||||
'journey.studio.mapSourceVector': 'Konturer',
|
||||
'journey.studio.mapSourceRelief': 'Relief',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Byt typsnitt',
|
||||
'journey.studio.fonts.search': 'Sök typsnitt',
|
||||
'journey.studio.fonts.list': 'Typsnitt',
|
||||
'journey.studio.fonts.used': 'Används i den här boken',
|
||||
'journey.studio.fonts.none': 'Inget typsnitt matchar',
|
||||
'journey.studio.fonts.range': '{from} till {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Sanserif',
|
||||
'journey.studio.fonts.cat.serif': 'Antikva',
|
||||
'journey.studio.fonts.cat.elegant': 'Eleganta rubriker',
|
||||
'journey.studio.fonts.cat.condensed': 'Smal och affisch',
|
||||
'journey.studio.fonts.cat.retro': 'Tung och retro',
|
||||
'journey.studio.fonts.cat.script': 'Skrivstil och handskrift',
|
||||
'journey.studio.fonts.cat.mono': 'Fast bredd',
|
||||
'journey.studio.weight.100': 'Tunn',
|
||||
'journey.studio.weight.200': 'Extra lätt',
|
||||
'journey.studio.weight.300': 'Lätt',
|
||||
'journey.studio.weight.400': 'Normal',
|
||||
'journey.studio.weight.500': 'Mellan',
|
||||
'journey.studio.weight.600': 'Halvfet',
|
||||
'journey.studio.weight.700': 'Fet',
|
||||
'journey.studio.weight.800': 'Extra fet',
|
||||
'journey.studio.weight.900': 'Svart',
|
||||
'journey.studio.weightOnly': 'Bara en vikt',
|
||||
'journey.studio.italicSlanted': 'Kursiv, lutad: det här typsnittet har ingen egen kursiv',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -569,7 +569,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'โมเสก',
|
||||
'journey.studio.tpl.bandQuote': 'คำระหว่าง',
|
||||
'journey.studio.tpl.staggerFour': '4 ภาพเหลื่อมกัน',
|
||||
'journey.studio.weightMissing': 'แบบอักษรนี้ไม่ได้มีน้ำหนักขนาดนั้น',
|
||||
'journey.studio.mapSource': 'แหล่งที่มาของแผนที่',
|
||||
'journey.studio.mapSourceVector': 'โครงร่าง',
|
||||
'journey.studio.mapSourceRelief': 'ภูมิประเทศ',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'เปลี่ยนแบบอักษร',
|
||||
'journey.studio.fonts.search': 'ค้นหาแบบอักษร',
|
||||
'journey.studio.fonts.list': 'แบบอักษร',
|
||||
'journey.studio.fonts.used': 'ใช้ในหนังสือเล่มนี้',
|
||||
'journey.studio.fonts.none': 'ไม่มีแบบอักษรที่ตรงกัน',
|
||||
'journey.studio.fonts.range': '{from} ถึง {to}',
|
||||
'journey.studio.fonts.cat.sans': 'ไม่มีเชิง',
|
||||
'journey.studio.fonts.cat.serif': 'มีเชิง',
|
||||
'journey.studio.fonts.cat.elegant': 'หัวเรื่องหรูหรา',
|
||||
'journey.studio.fonts.cat.condensed': 'แคบและโปสเตอร์',
|
||||
'journey.studio.fonts.cat.retro': 'หนาและย้อนยุค',
|
||||
'journey.studio.fonts.cat.script': 'ลายมือและอักษรวิจิตร',
|
||||
'journey.studio.fonts.cat.mono': 'ความกว้างคงที่',
|
||||
'journey.studio.weight.100': 'บางที่สุด',
|
||||
'journey.studio.weight.200': 'บางพิเศษ',
|
||||
'journey.studio.weight.300': 'บาง',
|
||||
'journey.studio.weight.400': 'ปกติ',
|
||||
'journey.studio.weight.500': 'กลาง',
|
||||
'journey.studio.weight.600': 'กึ่งหนา',
|
||||
'journey.studio.weight.700': 'หนา',
|
||||
'journey.studio.weight.800': 'หนาพิเศษ',
|
||||
'journey.studio.weight.900': 'ดำ',
|
||||
'journey.studio.weightOnly': 'มีน้ำหนักเดียว',
|
||||
'journey.studio.italicSlanted': 'ตัวเอียงแบบเอียง: แบบอักษรนี้ไม่มีตัวเอียงของตัวเอง',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -585,7 +585,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Mozaik',
|
||||
'journey.studio.tpl.bandQuote': 'Arada sözler',
|
||||
'journey.studio.tpl.staggerFour': 'Kaydırılmış dört',
|
||||
'journey.studio.weightMissing': 'Bu yazı tipinde bu kalınlık yok',
|
||||
'journey.studio.mapSource': 'Harita kaynağı',
|
||||
'journey.studio.mapSourceVector': 'Dış hatlar',
|
||||
'journey.studio.mapSourceRelief': 'Kabartma',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Yazı tipini değiştir',
|
||||
'journey.studio.fonts.search': 'Yazı tipi ara',
|
||||
'journey.studio.fonts.list': 'Yazı tipleri',
|
||||
'journey.studio.fonts.used': 'Bu kitapta kullanılanlar',
|
||||
'journey.studio.fonts.none': 'Eşleşen yazı tipi yok',
|
||||
'journey.studio.fonts.range': '{from} ile {to} arası',
|
||||
'journey.studio.fonts.cat.sans': 'Tırnaksız',
|
||||
'journey.studio.fonts.cat.serif': 'Tırnaklı',
|
||||
'journey.studio.fonts.cat.elegant': 'Zarif başlık',
|
||||
'journey.studio.fonts.cat.condensed': 'Dar ve afiş',
|
||||
'journey.studio.fonts.cat.retro': 'Kalın ve retro',
|
||||
'journey.studio.fonts.cat.script': 'El yazısı ve kaligrafi',
|
||||
'journey.studio.fonts.cat.mono': 'Eş aralıklı',
|
||||
'journey.studio.weight.100': 'İnce',
|
||||
'journey.studio.weight.200': 'Ekstra hafif',
|
||||
'journey.studio.weight.300': 'Hafif',
|
||||
'journey.studio.weight.400': 'Normal',
|
||||
'journey.studio.weight.500': 'Orta',
|
||||
'journey.studio.weight.600': 'Yarı kalın',
|
||||
'journey.studio.weight.700': 'Kalın',
|
||||
'journey.studio.weight.800': 'Ekstra kalın',
|
||||
'journey.studio.weight.900': 'Siyah',
|
||||
'journey.studio.weightOnly': 'Tek kalınlık',
|
||||
'journey.studio.italicSlanted': 'İtalik, eğik: bu yazı tipinin kendi italiği yok',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -606,7 +606,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Мозаїка',
|
||||
'journey.studio.tpl.bandQuote': 'Слова посередині',
|
||||
'journey.studio.tpl.staggerFour': 'Чотири зі зсувом',
|
||||
'journey.studio.weightMissing': 'У цієї гарнітури немає такого накреслення',
|
||||
'journey.studio.mapSource': 'Джерело карти',
|
||||
'journey.studio.mapSourceVector': 'Контури',
|
||||
'journey.studio.mapSourceRelief': 'Рельєф',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Змінити шрифт',
|
||||
'journey.studio.fonts.search': 'Пошук шрифтів',
|
||||
'journey.studio.fonts.list': 'Шрифти',
|
||||
'journey.studio.fonts.used': 'Використано в цій книзі',
|
||||
'journey.studio.fonts.none': 'Немає відповідних шрифтів',
|
||||
'journey.studio.fonts.range': 'від {from} до {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Без зарубок',
|
||||
'journey.studio.fonts.cat.serif': 'Із зарубками',
|
||||
'journey.studio.fonts.cat.elegant': 'Вишукані заголовкові',
|
||||
'journey.studio.fonts.cat.condensed': 'Вузькі та плакатні',
|
||||
'journey.studio.fonts.cat.retro': 'Важкі та ретро',
|
||||
'journey.studio.fonts.cat.script': 'Рукописні та каліграфічні',
|
||||
'journey.studio.fonts.cat.mono': 'Моноширинні',
|
||||
'journey.studio.weight.100': 'Тонкий',
|
||||
'journey.studio.weight.200': 'Надсвітлий',
|
||||
'journey.studio.weight.300': 'Світлий',
|
||||
'journey.studio.weight.400': 'Звичайний',
|
||||
'journey.studio.weight.500': 'Середній',
|
||||
'journey.studio.weight.600': 'Напівжирний',
|
||||
'journey.studio.weight.700': 'Жирний',
|
||||
'journey.studio.weight.800': 'Наджирний',
|
||||
'journey.studio.weight.900': 'Чорний',
|
||||
'journey.studio.weightOnly': 'Лише одне накреслення',
|
||||
'journey.studio.italicSlanted': 'Курсив, нахилений: цей шрифт не має власного курсиву',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -576,7 +576,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': 'Khảm',
|
||||
'journey.studio.tpl.bandQuote': 'Chữ ở giữa',
|
||||
'journey.studio.tpl.staggerFour': 'Bốn so le',
|
||||
'journey.studio.weightMissing': 'Phông chữ này không có độ đậm đó',
|
||||
'journey.studio.mapSource': 'Nguồn bản đồ',
|
||||
'journey.studio.mapSourceVector': 'Đường viền',
|
||||
'journey.studio.mapSourceRelief': 'Địa hình',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': 'Đổi phông chữ',
|
||||
'journey.studio.fonts.search': 'Tìm phông chữ',
|
||||
'journey.studio.fonts.list': 'Phông chữ',
|
||||
'journey.studio.fonts.used': 'Đã dùng trong sách này',
|
||||
'journey.studio.fonts.none': 'Không có phông chữ phù hợp',
|
||||
'journey.studio.fonts.range': '{from} đến {to}',
|
||||
'journey.studio.fonts.cat.sans': 'Không chân',
|
||||
'journey.studio.fonts.cat.serif': 'Có chân',
|
||||
'journey.studio.fonts.cat.elegant': 'Tiêu đề thanh lịch',
|
||||
'journey.studio.fonts.cat.condensed': 'Hẹp và áp phích',
|
||||
'journey.studio.fonts.cat.retro': 'Đậm và cổ điển',
|
||||
'journey.studio.fonts.cat.script': 'Viết tay và thư pháp',
|
||||
'journey.studio.fonts.cat.mono': 'Đơn cách',
|
||||
'journey.studio.weight.100': 'Mảnh',
|
||||
'journey.studio.weight.200': 'Rất nhẹ',
|
||||
'journey.studio.weight.300': 'Nhẹ',
|
||||
'journey.studio.weight.400': 'Thường',
|
||||
'journey.studio.weight.500': 'Vừa',
|
||||
'journey.studio.weight.600': 'Hơi đậm',
|
||||
'journey.studio.weight.700': 'Đậm',
|
||||
'journey.studio.weight.800': 'Rất đậm',
|
||||
'journey.studio.weight.900': 'Đen',
|
||||
'journey.studio.weightOnly': 'Chỉ một độ đậm',
|
||||
'journey.studio.italicSlanted': 'Nghiêng, được làm nghiêng: phông chữ này không có kiểu nghiêng riêng',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -113,6 +114,7 @@ const locale = {
|
||||
...todo,
|
||||
...notifications,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...notif,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
|
||||
@@ -567,7 +567,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': '馬賽克',
|
||||
'journey.studio.tpl.bandQuote': '文字置中',
|
||||
'journey.studio.tpl.staggerFour': '四張錯落',
|
||||
'journey.studio.weightMissing': '此字體沒有這個字重',
|
||||
'journey.studio.mapSource': '地圖來源',
|
||||
'journey.studio.mapSourceVector': '輪廓',
|
||||
'journey.studio.mapSourceRelief': '地形暈渲',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
/** The Studio's typeface picker and its weights. */
|
||||
const studioFonts: TranslationStrings = {
|
||||
'journey.studio.fonts.change': '更換字體',
|
||||
'journey.studio.fonts.search': '搜尋字體',
|
||||
'journey.studio.fonts.list': '字體',
|
||||
'journey.studio.fonts.used': '本書已使用',
|
||||
'journey.studio.fonts.none': '沒有符合的字體',
|
||||
'journey.studio.fonts.range': '{from} 至 {to}',
|
||||
'journey.studio.fonts.cat.sans': '無襯線',
|
||||
'journey.studio.fonts.cat.serif': '襯線',
|
||||
'journey.studio.fonts.cat.elegant': '優雅標題',
|
||||
'journey.studio.fonts.cat.condensed': '窄體與海報',
|
||||
'journey.studio.fonts.cat.retro': '粗體與復古',
|
||||
'journey.studio.fonts.cat.script': '手寫與書法',
|
||||
'journey.studio.fonts.cat.mono': '等寬',
|
||||
'journey.studio.weight.100': '極細',
|
||||
'journey.studio.weight.200': '特細',
|
||||
'journey.studio.weight.300': '細',
|
||||
'journey.studio.weight.400': '標準',
|
||||
'journey.studio.weight.500': '中等',
|
||||
'journey.studio.weight.600': '半粗',
|
||||
'journey.studio.weight.700': '粗',
|
||||
'journey.studio.weight.800': '特粗',
|
||||
'journey.studio.weight.900': '黑',
|
||||
'journey.studio.weightOnly': '僅一種字重',
|
||||
'journey.studio.italicSlanted': '斜體(傾斜):此字體沒有自己的斜體',
|
||||
};
|
||||
|
||||
export default studioFonts;
|
||||
@@ -58,6 +58,7 @@ import settings from './settings';
|
||||
import share from './share';
|
||||
import shared from './shared';
|
||||
import storage from './storage';
|
||||
import studioFonts from './studioFonts';
|
||||
import system_notice from './system_notice';
|
||||
import todo from './todo';
|
||||
import tourCollection from './tourCollection';
|
||||
@@ -114,6 +115,7 @@ const locale = {
|
||||
...todo,
|
||||
...notif,
|
||||
...journey,
|
||||
...studioFonts,
|
||||
...oauth,
|
||||
...system_notice,
|
||||
...transport,
|
||||
|
||||
@@ -567,7 +567,6 @@ const journey: TranslationStrings = {
|
||||
'journey.studio.tpl.mosaic': '马赛克',
|
||||
'journey.studio.tpl.bandQuote': '文字居中',
|
||||
'journey.studio.tpl.staggerFour': '四张错落',
|
||||
'journey.studio.weightMissing': '该字体没有此字重',
|
||||
'journey.studio.mapSource': '地图来源',
|
||||
'journey.studio.mapSourceVector': '轮廓',
|
||||
'journey.studio.mapSourceRelief': '地形晕渲',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user