Version 3.6.1 · All versions

All pages

These are the docs as released with Solid Memo 3.6.1. Read the latest docs.

Languages

Solid Memo speaks English (en), Swedish (sv), Korean (ko), German (de), Spanish (es) and French (fr). Every text the app itself shows comes from a message file; text that comes with the data — deck titles and descriptions, card text — is shown as the data has it, in the language the user says it is in (see Text in the user's languages).

Which language

pickLocale (locale.ts) decides, in order:

  1. the language the user chose — with the language list (a globe and the language's name in itself) at the top of every screen, or under Language in the study preferences — kept on this device by the LanguagePreference port (localStorageLanguagePreference.ts, key solid-memo:language) — not in the pod, since the sign-in screens need it before any pod is reached;
  2. else the first of the browser's preferred languages (navigator.languages) the app speaks — sv-SE counts as sv;
  3. else English.

AppShell, the shell of Solid Memo and of the Studio, sets <html lang> to it. Deck and card text is shown in the same language, else the browser's, else English, else the first language it has (shown in langText.ts); text in another language than the page's is marked with its own lang (readerLang, ReaderText), none for untagged text or text in no language (zxx).

A deck's keywords, several per language, are shown only in the chosen language: every keyword under a tag with its primary subtag (sv, sv-se, sv-fi for Swedish), with those in no stated language (untagged and zxx), each once (keywordsIn in keywords.ts). There is no fallback: a deck with none of those shows no keywords. Library decks carry their title, description and keywords in every language the app speaks, so a reader of any of them sees keywords on every library deck. The library's search still looks through the keywords of every language, as it does titles and descriptions.

Text in the user's languages

Text the user writes — a deck's name, description and keywords, a card's sides, notes, labels and picture descriptions — is saved language-tagged, one value per language (keywords: a comma-separated list per language), in whatever language the user says it is in (deck format 6, card format 6: shapes.md). The page's language says nothing about it: an English speaker on a Swedish page types English. So:

Older data may not say its language: card sides saved untagged, as the app wrote them before card format 5, and keywords saved before deck format 6. Their language is not guessed. Untagged keywords are kept, or removed, as they are; a keyword added or changed needs its language. The deck's page says so while any is left (DeckLanguagesNotice), linking to the Languages section of the deck's preferences (DeckLanguagesSection), where the untagged fronts or backs are given one language at once (stateCardLanguages); the Browser marks the cards with a side in no stated language and can list only those (languages=unstated in its URL). Text still as its library release has it is left alone, so a later release can update it.

The same words under several languages are text in each of them, and are accepted as they are: "Stockholm"@en with "Stockholm"@sv, like "1969"@sv with "1969"@it. That includes the identical English copies the app wrote while the formats required English: the app cannot tell them from a real translation, so they are left in the data untouched, neither flagged nor offered for removal. Each language is an entry of its own in the editor, edited, retagged or removed like any other translation.

The deck's page weighs its text itself (deckLanguageIssues over deckLanguages, pure domain functions) from the cards it has read already and the deck's library release (UseCases.deckRelease), rather than through a use case of its own that would read the cards a second time. When the release cannot be read, the page cannot tell the library's text from the user's: if the deck's text holds none to settle even counting the library's, nothing is said; otherwise the notice says the check could not be made, rather than counts, and still links to the Languages section, which shows why.

Formatted text

Text in Markdown (markdown.md) changes nothing of the above: each language's value is its own Markdown document, under its own tag, and every rule here holds of it as of plain text — shownTag, textNeedsLanguage, one text per language, a distractor in the back's languages. A code block inside @en text keeps @en, and a code-only back may be zxx. The card editor hints at each language's text under that text, and its preview shows the main one, the one being written.

The app's own words about Markdown are messages like any other: cardContentFields.markdown* and .preview* for the editor, markdownHelp.* for its cheat sheet (the syntax itself is code, the same in every language), markdownProblem.* for each problem markdownProblems names, and markdown.* for the names of a code block and a table region and a link's host.

Messages

packages/ui/src/i18n/en.json and the other languages' files beside it (sv.json, ko.json, de.json, es.json, fr.json) hold one namespace per component (deckList, instancePicker, …), a few shared ones (common, language, footer), and studio for the Studio's own screens, in apps/studio, and the page's while it loads them. Components read them through useI18n() (i18n.tsx):

const { t, tx, readerText, formatDate, directionLabel } = useI18n();
t("deckList.heading");                          // "Decks"
t("common.cardCount", { count: 3 });            // "3 cards" — a plural message
tx("app.loggedInAs", { name: <ExternalLink … /> }); // markup inside a sentence

Tests in i18n.test.tsx hold every other message file to exactly the keys and placeholders of en.json, and every key the source asks for to one en.json has.

Components outside a provider speak English, so component tests query English text without any setup.

Translations

English is the source; Swedish is the maintainer's own. Korean, German, Spanish and French were translated with the help of AI: a draft by one model following a glossary per language (register, typography, the words for deck, card, review…), then an independent check that translates each message back into English and fixes what drifted. No native speaker has reviewed them yet, so corrections from one are welcome. German says du, Spanish tú (neutral, international), French vous, Korean the polite 해요 style.

The About page, a static page for search engines and for people deciding whether to use Solid Memo, is in English only for now. It is outside the app and its catalogs, so it has no translations. A page per language, linked to each other with hreflang, can come later.

These docs, which the site also serves at /docs/ (deployment.md), are in English only, and their pages say so (lang="en"). They are written for contributors and those who run a copy as much as for learners, and change with the code, so they are not translated. The app's footer links to them with hreflang="en", and its label says "in English" in every other language (footer.docs).

Adding a language

  1. Add its code to LOCALES in packages/domain/src/locale.ts.
  2. Add packages/ui/src/i18n/<code>.json with every key of en.json, and register it in CATALOGS in i18n.tsx.
  3. Name it in itself in LANGUAGE_NAMES in LanguageSelector.tsx.
  4. Give each plural message exactly the forms its plural rules name (new Intl.PluralRules("<code>").resolvedOptions().pluralCategories): only other in Korean, Japanese and Chinese; one, many and other in Spanish and French (many for a million and more); one, few, many and other in Polish and Ukrainian.
  5. Register the file in the journeys' harness too: Locale and CATALOGS in e2e/journeys/harness/strings.ts, and the language's name in switchLanguage in e2e/journeys/pages/Chrome.ts.
  6. Extend the tests that name every language: locale.test.ts, i18n.test.tsx (its catalogs, a plural, a date, a language's name), LanguageSelector.test.tsx, PreferencesScreen.test.tsx.
  7. Name and define every topic in it in ns/vocab/topics.ttl, which holds its topics in the app's languages (vocab.md): the scheme's dcterms:title and skos:definition, and each concept's skos:prefLabel and skos:definition, with owl:versionInfo bumped and a skos:changeNote saying so. Then run npm run format:turtle, npm run generate and npm run crosscheck, and update the tests that compare topic labels (concepts.test.ts, library.test.ts).
  8. If its script needs its own fonts, line breaking or heading treatment, add :lang(<code>) rules to packages/ui/src/style.css, with tests in style.test.ts, as Korean has (theme.md). The Studio's headings are in the body's type and weight whatever the language (apps/studio/src/ui/studio.css), so look at them there too.
  9. A language written right to left, as the app's own, needs more than its text's direction (which the style sheet already sets): the page itself mirrored (dir="rtl" on <html>) and every layout rule that names a side made logical. No language the app speaks needs that yet.

The shapes' sh:messages stay in English and Swedish: a new language needs none, and its readers see a shape's own message in English, marked so, and the validator's messages in their language (shapes.md).