Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@
"build:sponsored:win": "vite build && npm run build:main && electron-builder --config electron-builder.sponsored.json --win --x64",
"build:sponsored:linux": "vite build && npm run build:main && electron-builder --config electron-builder.sponsored.json --linux --x64",
"build:all": "vite build && npm run build:main && npm run build:mac && npm run build:win && npm run build:linux",
"build:main": "npm run copy:locales && tsc -p tsconfig.main.json",
"build:main": "npm run i18n:copy && tsc -p tsconfig.main.json",
"api:generate": "node scripts/api-generate.js",
"copy:locales": "node scripts/copy-locales.js",
"i18n:copy": "node scripts/i18n/copy-locales.js",
"i18n:check": "node scripts/i18n/check-locale-parity.mjs",
"bench:load-test": "node scripts/bench-load-test.js",
"bench:seed": "node scripts/bench-seed.js",
"test": "vitest run",
Expand Down
93 changes: 93 additions & 0 deletions scripts/i18n/check-locale-parity.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { readdirSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import process from 'node:process'

const localesDir = join(import.meta.dirname, '../../src/main/i18n/locales')
const referenceLocale = 'en_US'

function flattenKeys(obj, prefix = '') {
const keys = []

for (const [key, value] of Object.entries(obj)) {
const path = prefix ? `${prefix}.${key}` : key

if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
keys.push(...flattenKeys(value, path))
}
else {
keys.push(path)
}
}

return keys
}

function readLocaleFile(locale, file) {
const filePath = join(localesDir, locale, file)

try {
const content = readFileSync(filePath, 'utf-8')
return JSON.parse(content)
}
catch {
return null
}
}

const locales = readdirSync(localesDir, { withFileTypes: true })
.filter(d => d.isDirectory() && d.name !== referenceLocale)
.map(d => d.name)

const referenceFiles = readdirSync(join(localesDir, referenceLocale))
.filter(f => f.endsWith('.json'))

const referenceKeysByFile = new Map()

for (const file of referenceFiles) {
const data = readLocaleFile(referenceLocale, file)
referenceKeysByFile.set(file, flattenKeys(data))
}

let hasMissing = false

for (const locale of locales) {
const missingByFile = []

for (const file of referenceFiles) {
const refKeys = referenceKeysByFile.get(file)
const data = readLocaleFile(locale, file)

if (data === null) {
missingByFile.push({ file, missing: refKeys })
continue
}

const localeKeys = new Set(flattenKeys(data))
const missing = refKeys.filter(k => !localeKeys.has(k))

if (missing.length > 0) {
missingByFile.push({ file, missing })
}
}

if (missingByFile.length > 0) {
hasMissing = true
console.log(`\n[${locale}]`)

for (const { file, missing } of missingByFile) {
console.log(` ${file} — ${missing.length} missing key(s):`)

for (const key of missing) {
console.log(` - ${key}`)
}
}
}
}

if (hasMissing) {
console.log('')
process.exit(1)
}
else {
console.log('All locales in sync with en_US.')
}
4 changes: 2 additions & 2 deletions scripts/copy-locales.js → scripts/i18n/copy-locales.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ const fs = require('node:fs')
const path = require('node:path')
const { styleText } = require('node:util')

const sourceDir = path.join(__dirname, '../src/main/i18n/locales')
const targetDir = path.join(__dirname, '../build/main/i18n/locales')
const sourceDir = path.join(__dirname, '../../src/main/i18n/locales')
const targetDir = path.join(__dirname, '../../build/main/i18n/locales')

function copyDirRecursive(sourceDir, targetDir) {
if (!fs.existsSync(targetDir)) {
Expand Down
34 changes: 33 additions & 1 deletion src/main/i18n/locales/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,37 @@ Examples:
- **preferences.json** - Application settings and preferences
- **devtools.json** - Developer tools and utilities

## ui.json Key Structure

Keys in `ui.json` are organized around the Spaces architecture:

```
common.* — shared across spaces (inbox, favorites, trash, folders, tags, library, total, fragment)
spaces.* — space definitions and space-specific UI
spaces.label — "Spaces" aria-label
spaces.{id}.label — space name in rail navigation
spaces.{id}.tooltip — space tooltip
spaces.{id}.* — space-specific content (title, allSnippets, sheetList, etc.)
snippet.* — snippet entity keys (untitled, selectedMultiple, noSelected)
notes.* — note entity keys
folder.* — folder entity keys
button.* — reusable button labels
action.* — context-specific actions
placeholder.* — empty states and input placeholders
```

### Where to place new keys

| Key type | Location | Example |
|----------|----------|---------|
| Shared sidebar concept | `common.*` | `common.inbox` |
| Space label/tooltip | `spaces.{id}.label/tooltip` | `spaces.code.label` |
| Space-specific UI | `spaces.{id}.*` | `spaces.math.newSheet` |
| Entity property | `snippet.*`, `notes.*`, `folder.*` | `snippet.untitled` |
| Reusable button | `button.*` | `button.confirm` |
| Context action | `action.*` | `action.move.toTrash` |
| Empty state / placeholder | `placeholder.*` | `placeholder.emptySnippetsList` |

## Organization Principles

1. **Modularity**: Separate keys by functional blocks
Expand All @@ -35,10 +66,11 @@ Examples:

When adding new localization keys:

1. Determine which functional block your key belongs to
1. Determine which functional block your key belongs to (see ui.json Key Structure above)
2. Choose the appropriate file based on the content type
3. Follow the existing structure and naming conventions
4. Add the key to all language packs
5. Run `pnpm i18n:check` to verify all locales are in sync

## Adding New Languages

Expand Down
17 changes: 9 additions & 8 deletions src/main/i18n/locales/cs_CZ/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"update": "Zkontrolovat aktualizace...",
"quit": "Ukončit massCode",
"about": "O aplikaci massCode",
"hide": "Skrýt massCode"
"hide": "Skrýt massCode",
"mathNotebook": "Math Notebook"
},
"help": {
"label": "Nápověda",
Expand All @@ -32,20 +33,20 @@
"snippets": "Kolekce snippetů"
}
},
"edit": {
"label": "Upravit",
"find": "Najít"
},
"view": {
"label": "Zobrazení",
"showSidebar": "Zobrazit/skrýt postranní panel",
"sortBy": {
"label": "Seřadit snippety podle",
"dateModified": "Data úpravy",
"dateCreated": "Data vytvoření",
"name": "Názvu"
},
"compactMode": "Kompaktní režim",
"showSidebar": "Zobrazit/skrýt postranní panel"
"compactMode": "Kompaktní režim"
},
"edit": {
"label": "Upravit",
"find": "Najít"
},
"editor": {
"label": "Editor",
Expand All @@ -60,9 +61,9 @@
},
"markdown": {
"label": "Markdown",
"presentationMode": "Prezentační režim",
"preview": "Náhled",
"previewMarkdown": "Náhled Markdownu",
"presentationMode": "Prezentační režim",
"previewMindmap": "Náhled myšlenkové mapy"
},
"history": {
Expand Down
6 changes: 5 additions & 1 deletion src/main/i18n/locales/cs_CZ/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
]
},
"success": {
"copied": "Zkopírováno do schránky"
"copied": "Zkopírováno do schránky",
"migrateToMarkdown": "Migrated to Markdown Vault. Folders: {{folders}}, snippets: {{snippets}}, tags: {{tags}}.",
"vaultLoaded": "Vault successfully loaded."
},
"warning": {
"noUndo": "Tuto akci nelze vrátit zpět.",
Expand All @@ -27,13 +29,15 @@
"migration": "Auto-migration from SQLite failed: {{error}}"
},
"description": {
"storageVault": "Choose the vault directory. To sync between devices, select a folder in iCloud Drive, Google Drive or Dropbox.",
"language": "Pro aplikaci změny jazyka je nutné aplikaci znovu načíst.",
"migrateSqliteUtility": "If you have a massCode.db file from a previous version, use this utility to import your data into the current Markdown Vault."
},
"special": {
"supportMessage": "Ahoj, tady Anton 👋<br><br>\nDěkuji za používání massCode. Pokud vám tato aplikace přijde užitečná, prosím {{-tagStart}}přispějte{{-tagEnd}}. Bude mě to motivovat k dalšímu vývoji projektu.",
"unsponsored": "Bez sponzorů"
},
"release": {},
"update": {
"available": "Verze {{newVersion}} je nyní k dispozici ke stažení.\nVaše verze je {{oldVersion}}.",
"noAvailable": "V současné době nejsou k dispozici žádné aktualizace."
Expand Down
43 changes: 42 additions & 1 deletion src/main/i18n/locales/cs_CZ/preferences.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"migration": "Migration"
},
"label": "Úložiště",
"migrateSqliteToMarkdown": "Migrate to Markdown Vault",
"count": "Počet",
"migrateSqliteToMarkdown": "Migrate to Markdown Vault"
"vaultPath": "Vault Path"
},
"editor": {
"label": "Editor",
Expand Down Expand Up @@ -35,10 +36,50 @@
"singleQuote": "Jednoduché uvozovky"
}
},
"notesEditor": {
"label": "Note Editor",
"fontSize": {
"label": "Font Size",
"description": "Base font size in pixels for the notes editor."
},
"fontFamily": {
"label": "Text Font",
"description": "CSS font-family value. Use commas to specify fallbacks."
},
"codeFontFamily": {
"label": "Monospace Font",
"description": "Font for inline code and fenced code blocks."
},
"lineHeight": {
"label": "Line Height",
"description": "Spacing between lines. Relaxed is easier to read, compact fits more content.",
"compact": "Compact (1.4)",
"default": "Default (1.54)",
"relaxed": "Relaxed (1.7)"
},
"indentSize": {
"label": "Indent Size",
"description": "Number of spaces per indent level for lists and nested content."
},
"limitWidth": {
"label": "Limit Width",
"description": "Constrain content to a comfortable reading width (700px)."
},
"lineNumbers": {
"label": "Line Numbers",
"description": "Show line numbers in the gutter. Only available in Raw mode."
}
},
"appearance": {
"label": "Vzhled",
"theme": {
"label": "Motiv",
"builtIn": "Built-in",
"custom": "Custom",
"themesDir": "Themes Directory",
"openDir": "Open Themes Directory",
"createTemplate": "Create Theme Template",
"dirDescription": "Place your JSON theme files in this folder.",
"light": "Světlý",
"dark": "Tmavý",
"system": "Systémový"
Expand Down
Loading