From 6ca0fcdec262ed3ef3372d61611c3ff9f55cce6d Mon Sep 17 00:00:00 2001 From: Anthony Riley Date: Fri, 16 Oct 2026 14:47:56 +0200 Subject: [PATCH 1/3] feat: add translation core and language files for nine languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A small typed dictionary: English is the source, and each language is a JSON file that must have the same keys and placeholders (checked by tests). Plurals use Intl.PluralRules, and numbers and dates use the app language. Languages load on demand; the saved choice is read before the first render. Languages: English, Svenska, Norsk bokmål, Suomi, Dansk, Español, Français, Português (Brasil), Português (Portugal). Legal text stays in English. Co-Authored-By: Claude Haiku 5.5 --- src/i18n/i18n.test.ts | 73 +++++++++++++ src/i18n/index.ts | 139 ++++++++++++++++++++++++ src/locales/da.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/en.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/es.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/fi.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/fr.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/nb.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/pt-BR.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/pt-PT.json | 232 +++++++++++++++++++++++++++++++++++++++++ src/locales/sv.json | 232 +++++++++++++++++++++++++++++++++++++++++ tsconfig.app.json | 1 + 12 files changed, 2301 insertions(+) create mode 100644 src/i18n/i18n.test.ts create mode 100644 src/i18n/index.ts create mode 100644 src/locales/da.json create mode 100644 src/locales/en.json create mode 100644 src/locales/es.json create mode 100644 src/locales/fi.json create mode 100644 src/locales/fr.json create mode 100644 src/locales/nb.json create mode 100644 src/locales/pt-BR.json create mode 100644 src/locales/pt-PT.json create mode 100644 src/locales/sv.json diff --git a/src/i18n/i18n.test.ts b/src/i18n/i18n.test.ts new file mode 100644 index 0000000..b96905f --- /dev/null +++ b/src/i18n/i18n.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest' +import en from '../locales/en.json' +import { LOCALES, matchLocale, t } from './index' + +const locales = import.meta.glob>('../locales/*.json', { + eager: true, + import: 'default', +}) + +const placeholders = (text: string) => [...text.matchAll(/\{(\w+)\}/g)].map((m) => m[1]).sort() + +describe('locale files', () => { + const englishKeys = Object.keys(en).sort() + + it('has a file for every supported language except English', () => { + const files = Object.keys(locales).map((path) => path.replace('../locales/', '').replace('.json', '')) + for (const { code } of LOCALES) { + if (code === 'en') continue + expect(files, `missing locales/${code}.json`).toContain(code) + } + }) + + for (const [path, dict] of Object.entries(locales)) { + const name = path.replace('../locales/', '') + + it(`${name} has exactly the English keys`, () => { + expect(Object.keys(dict).sort()).toEqual(englishKeys) + }) + + it(`${name} keeps every placeholder from English`, () => { + for (const key of englishKeys) { + expect(placeholders(dict[key]), key).toEqual(placeholders(en[key as keyof typeof en])) + } + }) + + it(`${name} has no empty translations`, () => { + for (const key of englishKeys) { + expect(dict[key].trim(), key).not.toBe('') + } + }) + } +}) + +describe('matchLocale', () => { + it('maps browser tags to supported languages', () => { + expect(matchLocale('sv-SE')).toBe('sv') + expect(matchLocale('nb-NO')).toBe('nb') + expect(matchLocale('no')).toBe('nb') + expect(matchLocale('nn-NO')).toBe('nb') + expect(matchLocale('fi-FI')).toBe('fi') + expect(matchLocale('da')).toBe('da') + expect(matchLocale('es-MX')).toBe('es') + expect(matchLocale('fr-CA')).toBe('fr') + expect(matchLocale('pt-PT')).toBe('pt-PT') + expect(matchLocale('pt-BR')).toBe('pt-BR') + expect(matchLocale('pt')).toBe('pt-BR') + }) + + it('returns null for unsupported languages', () => { + expect(matchLocale('de-DE')).toBeNull() + expect(matchLocale('zh-CN')).toBeNull() + }) +}) + +describe('t', () => { + it('interpolates named placeholders', () => { + expect(t('rotation.degrees', { degrees: 45 })).toBe('45 degrees') + }) + + it('falls back to the English text for an untranslated key', () => { + expect(t('common.close')).toBe('Close') + }) +}) diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..cc9ae7f --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,139 @@ +import { useSyncExternalStore } from 'react' +import en from '../locales/en.json' + +/** Languages the app ships. English is always bundled; the rest load on demand. */ +export const LOCALES = [ + { code: 'en', name: 'English' }, + { code: 'sv', name: 'Svenska' }, + { code: 'nb', name: 'Norsk bokmål' }, + { code: 'fi', name: 'Suomi' }, + { code: 'da', name: 'Dansk' }, + { code: 'es', name: 'Español' }, + { code: 'fr', name: 'Français' }, + { code: 'pt-BR', name: 'Português (Brasil)' }, + { code: 'pt-PT', name: 'Português (Portugal)' }, +] as const + +export type Locale = (typeof LOCALES)[number]['code'] +export type MessageKey = keyof typeof en +/** Plain text, or per-plural-category text for counted messages */ +export type Message = string | { one: string; other: string } +/** Every locale file must satisfy this, so a missing key fails the type check */ +export type Dictionary = Record +export type MessageParams = Record + +const STORAGE_KEY = 'o-analysis-locale' + +const loaders = import.meta.glob<{ default: Dictionary }>('../locales/*.json') + +let currentLocale: Locale = 'en' +let current: Dictionary = en as Dictionary +const listeners = new Set<() => void>() + +function isLocale(value: string): value is Locale { + return LOCALES.some((l) => l.code === value) +} + +/** + * Picks the closest supported language for a browser language tag. + * Norwegian nn/no map to Bokmål; any Portuguese region other than Portugal + * maps to Brazilian Portuguese, which is the larger audience. + */ +export function matchLocale(tag: string): Locale | null { + const lower = tag.toLowerCase() + if (lower === 'pt-pt') return 'pt-PT' + if (lower === 'pt-br') return 'pt-BR' + const base = lower.split('-')[0] + if (base === 'pt') return lower.endsWith('-pt') ? 'pt-PT' : 'pt-BR' + if (base === 'no' || base === 'nn' || base === 'nb') return 'nb' + if (isLocale(base)) return base + return null +} + +export function detectLocale(): Locale { + try { + const saved = globalThis.localStorage?.getItem(STORAGE_KEY) + if (saved && isLocale(saved)) return saved + } catch { + // Storage can be blocked (private mode, disabled site data); fall through to the browser + } + for (const tag of globalThis.navigator?.languages ?? []) { + const match = matchLocale(tag) + if (match) return match + } + return 'en' +} + +async function loadDictionary(locale: Locale): Promise { + if (locale === 'en') return en as Dictionary + const loader = loaders[`../locales/${locale}.json`] + if (!loader) return en as Dictionary // unknown file: fall back rather than show raw keys + return (await loader()).default +} + +function applyDocumentLanguage(locale: Locale): void { + if (typeof document === 'undefined') return + document.documentElement.lang = locale + document.documentElement.dir = 'ltr' +} + +/** Loads the saved or detected language before the first render. */ +export async function initI18n(): Promise { + await setLocale(detectLocale(), { persist: false }) +} + +/** Switches language. Components re-render through useLocale(). */ +export async function setLocale( + locale: Locale, + options: { persist?: boolean } = { persist: true }, +): Promise { + current = await loadDictionary(locale) + currentLocale = locale + applyDocumentLanguage(locale) + if (options.persist !== false) { + try { + globalThis.localStorage?.setItem(STORAGE_KEY, locale) + } catch { + // Not saved; the choice lasts for this visit only + } + } + for (const listener of listeners) listener() +} + +export function getLocale(): Locale { + return currentLocale +} + +function interpolate(text: string, params: MessageParams = {}): string { + return text.replace(/\{(\w+)\}/g, (match, name: string) => + name in params ? String(params[name]) : match, + ) +} + +/** Translates a message. Counted messages pick the plural form from the `count` param. */ +export function t(key: MessageKey, params?: MessageParams): string { + const message: Message = current[key] ?? (en as Dictionary)[key] + if (typeof message === 'string') return interpolate(message, params) + const count = Number(params?.count ?? 0) + const category = new Intl.PluralRules(currentLocale).select(count) + return interpolate(category === 'one' ? message.one : message.other, params) +} + +function subscribe(listener: () => void): () => void { + listeners.add(listener) + return () => listeners.delete(listener) +} + +/** Returns the translator and re-renders the caller when the language changes. */ +export function useT(): (key: MessageKey, params?: MessageParams) => string { + useSyncExternalStore(subscribe, getLocale, getLocale) + return t +} + +/** Formats a number for the app language, e.g. 12,5 in Swedish and 12.5 in English. */ +export function formatNumber(value: number, fractionDigits = 0): string { + return new Intl.NumberFormat(currentLocale, { + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + }).format(value) +} diff --git a/src/locales/da.json b/src/locales/da.json new file mode 100644 index 0000000..6c7b1d1 --- /dev/null +++ b/src/locales/da.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Redigér GPX", + "action.editImage": "Redigér billede", + "action.export": "Eksportér", + "action.importGpx": "Importér GPX", + "action.importImage": "Importér billede", + "action.installApp": "Installér appen", + "action.newProject": "Nyt projekt", + "action.openProject": "Åbn projekt", + "action.projects": "Projekter", + "action.saveProject": "Gem projekt", + "action.updateApp": "Opdatér appen", + "canvas.importHintDrop": "Importér et kortbillede for at komme i gang, eller slip en fil her.", + "canvas.importHintTouch": "Importér et kortbillede for at komme i gang.", + "canvas.importMap": "Importér kort", + "canvas.loadingSample": "Indlæser eksempel…", + "canvas.noMap": "Intet kort indlæst", + "canvas.openSample": "Åbn eksempelprojekt", + "color.channel": "{channel}-kanal", + "color.channelValue": "{channel}-værdi", + "color.custom": "Egen farve", + "color.hex": "Hex", + "color.hexAria": "Hex-farve", + "common.apply": "Anvend", + "common.cancel": "Annullér", + "common.close": "Luk", + "common.delete": "Slet", + "common.dismiss": "Luk", + "common.errorGeneric": "Noget gik galt", + "common.loading": "Indlæser…", + "common.rename": "Omdøb", + "error.body": "O-Analysis stødte på en uventet fejl. Genindlæsning af siden hjælper normalt.", + "error.damagedEntry": "Ugyldig {label} i projektfilen (post {index} er beskadiget)", + "error.decodeImage": "Billedfilen kunne ikke læses", + "error.decodeMap": "Kortbilledet kunne ikke læses", + "error.exportTooLarge": "Kortet er for stort til at blive eksporteret på denne enhed", + "error.gpxImport": "GPX-importen mislykkedes", + "error.imageImport": "Billedimporten mislykkedes", + "error.imageTooLarge": "Billedet er for stort ({size}). Den maksimale størrelse er 20 MB.", + "error.invalidEntry": "Ugyldig {label} i projektfilen", + "error.invalidMap": "Ugyldigt kortbillede i projektfilen", + "error.invalidName": "Ugyldigt projektnavn i projektfilen", + "error.invalidRotation": "Ugyldig rotation i projektfilen", + "error.labelAnnotations": "annoteringer", + "error.labelDrawings": "tegninger", + "error.labelTracks": "GPX-spor", + "error.loadSample": "Eksempelprojektet kunne ikke indlæses", + "error.noGpxPoints": "GPX-filen indeholder ingen brugbare sporpunkter", + "error.notGpx": "Ikke en gyldig GPX-fil", + "error.notImage": "”{name}” er ikke en billedfil", + "error.notJson": "Projektfilen er ikke gyldig JSON", + "error.notOurs": "Ikke en O-Analysis-projektfil", + "error.notProjectFile": "Ikke en projektfil – forventede {ext}", + "error.nothingToSave": "Intet at gemme – importér et kort eller tilføj indhold først", + "error.openProject": "Projektfilen kunne ikke åbnes", + "error.projectNotFound": "Projektet kunne ikke findes", + "error.readImage": "Billedfilen kunne ikke læses", + "error.reload": "Genindlæs siden", + "error.renderExport": "Eksportbilledet kunne ikke tegnes", + "error.sampleNotFound": "Eksempelprojektet blev ikke fundet", + "error.title": "Noget gik galt", + "error.unsupportedFile": "Filtypen understøttes ikke: ”{name}”", + "error.version": "Projektversionen understøttes ikke: {version}", + "export.download": "Download PNG", + "export.exporting": "Eksporterer…", + "export.failed": "Eksporten mislykkedes – prøv igen", + "export.fit": "Tilpas", + "export.generating": "Genererer forhåndsvisning…", + "export.hint": "Rul for at zoome · træk for at flytte", + "export.includeSidebar": "Medtag annoteringspanelet", + "export.loadingPreview": "Indlæser forhåndsvisning…", + "export.previewAlt": "Forhåndsvisning af eksport", + "export.resetZoom": "Nulstil zoom", + "export.scaleNotice": "Kortet er større, end denne enhed kan eksportere i fuld størrelse, så PNG-filen bliver {percent} % af den oprindelige opløsning.", + "export.sidebarWithComments": "Kommentarerne vises på kortet – slå panelet fra for at undgå dubletter.", + "export.sidebarWithLabels": "Panelet viser hver markørbogstav med sin kommentar.", + "export.textSize": "Tekststørrelse", + "export.title": "Eksportér", + "export.zoomIn": "Zoom ind", + "export.zoomOut": "Zoom ud", + "gpx.adjustMap": "Justér på kortet", + "gpx.applyCrop": "Anvend beskæring", + "gpx.confirm": "Bekræft", + "gpx.crop": "Beskær", + "gpx.cropAria": "Beskær spor", + "gpx.cropError": "Markeringen skal indeholde mindst 2 punkter.", + "gpx.cropTitle": "Beskær spor", + "gpx.empty": "Ingen GPX-spor indlæst.", + "gpx.fast": "Hurtig (grøn)", + "gpx.highHr": "Høj puls (lys rød)", + "gpx.hr": "Puls", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Linjestil", + "gpx.lowHr": "Lav puls (mørk rød)", + "gpx.noTiming": "Ingen tidsdata i denne GPX-fil – beskær efter afstand", + "gpx.opacity": "Opacitet", + "gpx.opacityShort": "Opac.", + "gpx.pace": "Tempo", + "gpx.remove": "Fjern", + "gpx.removeAria": "Fjern spor", + "gpx.slow": "Langsom (rød)", + "gpx.solid": "Helt optrukket", + "gpx.strokeWidth": "Linjebredde", + "gpx.tipAdjust": "Skift til kortet og træk ankerpunkterne", + "gpx.tipCrop": "Beskær sporets start og slutning", + "gpx.tipRemove": "Fjern dette spor fra projektet", + "gpx.title": "GPX-spor", + "gpx.toastTapHint": "Tryk på sporet for at tilføje punkter. Tryk længe på et punkt for at slette det.", + "gpx.trackColour": "Sporfarve {color}", + "gpx.upload": "Upload", + "gpx.width": "Bredde", + "language.label": "Sprog", + "legal.binding": "Den engelske version af dokumentet er den bindende tekst. Oversættelserne er kun til orientering.", + "legal.lastUpdated": "Senest opdateret: {date}", + "library.deleteAria": "Slet {name}", + "library.empty": "Ingen gemte projekter endnu. Importér et kort for at starte et.", + "library.noMap": "Intet kort endnu", + "library.open": "Åben", + "library.renameAria": "Omdøb {name}", + "library.title": "Projekter", + "library.updated": "{map} · Opdateret {date}", + "menu.file": "Fil", + "menu.legal": "Juridisk", + "menu.menu": "Menu", + "menu.more": "Flere handlinger", + "menu.privacy": "Privatlivspolitik", + "menu.terms": "Servicevilkår", + "notes.centerHint": "Klik for at centrere på kortet", + "notes.commentPlaceholder": "Tilføj en kommentar…", + "notes.comments": "Kommentarer", + "notes.commentsHint": "Vis kommentarer direkte på kortet. Træk i højre kant af en valgt kommentar for at ændre bredden.", + "notes.deleteAria": "Slet annotering {label}", + "notes.displayAria": "Visning af kortmarkører", + "notes.empty": "Ingen annoteringer endnu. Brug markørværktøjet til at tilføje kommentarer på kortet.", + "notes.heading": "Annoteringer", + "notes.labels": "Etiketter", + "notes.labelsHint": "Vis bogstavetiketter på kortmarkørerne", + "notes.removeHint": "Fjern denne markør", + "notes.toggle": "Noter", + "notes.toggleAria": "Vis eller skjul annoteringer", + "project.untitled": "Unavngivet projekt", + "pwa.dismissUpdate": "Luk opdateringsbeskeden", + "pwa.updateAvailable": "En ny version af O-Analysis er tilgængelig.", + "pwa.updateNow": "Opdatér nu", + "rotation.degrees": "{degrees} grader", + "rotation.group": "Kortrotation", + "rotation.label": "Rotation", + "rotation.reset": "Nulstil rotation", + "rotation.resetAria": "Nulstil rotationen til 0 grader", + "rotation.rotate": "Roter", + "save.failed": "Automatisk gemning mislykkedes", + "save.saved": "Gemt {time}", + "save.saving": "Gemmer…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "af Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom} %. Tilpas kortet til skærmen", + "status.fitHint": "Tilpas kortet til skærmen", + "status.legal": "Juridisk", + "status.noCoords": "X: — Y: —", + "status.privacy": "Privatliv", + "status.terms": "Vilkår", + "status.textSize": "Tekststørrelse", + "status.textSizeShort": "Tekst", + "status.tool": "Værktøj: {tool}", + "status.zoomIn": "Zoom ind", + "status.zoomOut": "Zoom ud", + "stroke.color": "Farve", + "stroke.colorAria": "Farve {color}", + "stroke.custom": "Egen farve", + "stroke.customHint": "Vælg en egen farve", + "stroke.opacity": "Opacitet", + "stroke.opacityHint": "Gennemsigtighed for tegnede linjer og markører", + "stroke.useColor": "Brug {color}", + "stroke.width": "Bredde", + "stroke.widthHint": "Linje- og markørbredde", + "style.commentTextSize": "Tekststørrelse for kommentarer", + "tool.eraser": "Viskelæder", + "tool.eraser.tip": "Klik eller træk hen over linjer og markører for at fjerne dem.", + "tool.eraser.touchTip": "Tryk eller træk hen over linjer og markører for at fjerne dem.", + "tool.gpx": "Justér GPX", + "tool.gpx.tip": "Klik på sporet for at tilføje punkter, træk for at flytte dem, og højreklik på et punkt for at slette det.", + "tool.gpx.touchTip": "Tryk på sporet for at tilføje punkter. Tryk længe på et punkt for at slette det.", + "tool.gpxNeedsFile": "Importér en GPX-fil først", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Tegn", + "tool.line.tip": "Klik og træk for at tegne linjer på kortet.", + "tool.line.touchTip": "Træk fingeren for at tegne linjer på kortet.", + "tool.marker": "Markør", + "tool.marker.tip": "Klik for at placere en nummereret markør med en kommentar.", + "tool.marker.touchTip": "Tryk for at placere en nummereret markør med en kommentar.", + "tool.pan": "Flyt", + "tool.pan.tip": "Træk kortet for at bevæge dig rundt. Hold mellemrum eller midterknappen nede for midlertidig flytning.", + "tool.pan.touchTip": "Træk med ét finger, eller brug to fingre for at flytte i alle værktøjer.", + "tool.select": "Vælg", + "tool.select.tip": "Klik på linjer eller markører for at vælge dem. Tryk på Delete for at fjerne.", + "tool.select.touchTip": "Tryk på linjer eller markører for at vælge dem.", + "toolbar.annotations": "Annoteringer", + "toolbar.drawingTools": "Tegneværktøjer", + "toolbar.notes": "Noter", + "toolbar.strokeSettings": "Linjeindstillinger", + "toolbar.style": "Stil", + "toolbar.viewAndStroke": "Visning og linjeindstillinger", + "top.dismissError": "Luk fejlbeskeden", + "top.editGpxHint": "Ændr sporfarve, bredde eller justering", + "top.exportDisabled": "Importér et kortbillede, før du eksporterer", + "top.exportHint": "Forhåndsvis og download kortet med tegninger og annoteringer", + "top.history": "Historik", + "top.importGpxHint": "Importér en GPX-fil for at lægge en rute over kortet", + "top.importImageHint": "Upload et kortbillede – PNG, JPG, eller slip en fil på lærredet", + "top.installApp": "Installér appen", + "top.installHint": "Installér O-Analysis som en desktopapp", + "top.new": "Ny", + "top.newHint": "Ryd lærredet og start forfra", + "top.open": "Åbn", + "top.openHint": "Åbn et gemt projekt ({ext})", + "top.projectName": "Projektnavn", + "top.projects": "Projekter", + "top.projectsHint": "Skift mellem gemte projekter", + "top.redo": "Gentag", + "top.redoHint": "Gentag (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Erstat det aktuelle kortbillede", + "top.save": "Gem", + "top.saveDisabled": "Intet at gemme endnu – importér et kort eller tilføj annoteringer først", + "top.saveHint": "Gem projektet som {ext}", + "top.undo": "Fortryd", + "top.undoHint": "Fortryd sidste handling (⌘Z / Ctrl+Z)", + "top.updateApp": "Opdatér appen", + "top.updateHint": "Genindlæs for at bruge den seneste version", + "touch.dismissTips": "Luk tipsene", + "touch.tips": "Knib for at zoome, brug to fingre for at flytte. Tryk på et værktøj og derefter på kortet for at tegne eller placere markører." +} diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 0000000..4d6ea43 --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Edit GPX", + "action.editImage": "Edit Image", + "action.export": "Export", + "action.importGpx": "Import GPX", + "action.importImage": "Import Image", + "action.installApp": "Install App", + "action.newProject": "New Project", + "action.openProject": "Open Project", + "action.projects": "Projects", + "action.saveProject": "Save Project", + "action.updateApp": "Update App", + "canvas.importHintDrop": "Import a map image to get started, or drop a file here.", + "canvas.importHintTouch": "Import a map image to get started.", + "canvas.importMap": "Import Map", + "canvas.loadingSample": "Loading sample…", + "canvas.noMap": "No map loaded", + "canvas.openSample": "Open Sample Project", + "color.channel": "{channel} channel", + "color.channelValue": "{channel} value", + "color.custom": "Custom color", + "color.hex": "Hex", + "color.hexAria": "Hex color", + "common.apply": "Apply", + "common.cancel": "Cancel", + "common.close": "Close", + "common.delete": "Delete", + "common.dismiss": "Dismiss", + "common.errorGeneric": "Something went wrong", + "common.loading": "Loading…", + "common.rename": "Rename", + "error.body": "O-Analysis ran into an unexpected error. Reloading the page usually fixes this.", + "error.damagedEntry": "Invalid {label} in project file (entry {index} is damaged)", + "error.decodeImage": "Could not decode the image file", + "error.decodeMap": "Could not decode map image", + "error.exportTooLarge": "This map is too large to export on this device", + "error.gpxImport": "GPX import failed", + "error.imageImport": "Image import failed", + "error.imageTooLarge": "Image is too large ({size}). Maximum size is 20MB.", + "error.invalidEntry": "Invalid {label} in project file", + "error.invalidMap": "Invalid map image in project file", + "error.invalidName": "Invalid project name in project file", + "error.invalidRotation": "Invalid rotation in project file", + "error.labelAnnotations": "annotations", + "error.labelDrawings": "drawings", + "error.labelTracks": "GPX tracks", + "error.loadSample": "Failed to load sample project", + "error.noGpxPoints": "GPX file contains no usable track points", + "error.notGpx": "Not a valid GPX file", + "error.notImage": "\"{name}\" is not an image file", + "error.notJson": "Project file is not valid JSON", + "error.notOurs": "Not an O-Analysis project file", + "error.notProjectFile": "Not a project file — expected {ext}", + "error.nothingToSave": "Nothing to save — import a map or add content first", + "error.openProject": "Failed to open project file", + "error.projectNotFound": "That project could not be found", + "error.readImage": "Could not read the image file", + "error.reload": "Reload page", + "error.renderExport": "Failed to render export image", + "error.sampleNotFound": "Sample project not found", + "error.title": "Something went wrong", + "error.unsupportedFile": "Unsupported file type: \"{name}\"", + "error.version": "Unsupported project version: {version}", + "export.download": "Download PNG", + "export.exporting": "Exporting…", + "export.failed": "Export failed — please try again", + "export.fit": "Fit", + "export.generating": "Generating preview…", + "export.hint": "Scroll to zoom · drag to pan", + "export.includeSidebar": "Include annotation sidebar", + "export.loadingPreview": "Loading preview…", + "export.previewAlt": "Export preview", + "export.resetZoom": "Reset zoom", + "export.scaleNotice": "This map is larger than this device can export at full size, so the PNG will be {percent}% of native resolution.", + "export.sidebarWithComments": "Comments are shown on the map — turn off the sidebar to avoid duplicating them.", + "export.sidebarWithLabels": "The sidebar lists each marker letter with its comment.", + "export.textSize": "Text size", + "export.title": "Export", + "export.zoomIn": "Zoom in", + "export.zoomOut": "Zoom out", + "gpx.adjustMap": "Adjust on map", + "gpx.applyCrop": "Apply crop", + "gpx.confirm": "Confirm", + "gpx.crop": "Crop", + "gpx.cropAria": "Crop track", + "gpx.cropError": "Selection must include at least 2 points.", + "gpx.cropTitle": "Crop track", + "gpx.empty": "No GPX tracks loaded.", + "gpx.fast": "Fast (green)", + "gpx.highHr": "High HR (bright red)", + "gpx.hr": "HR", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Line style", + "gpx.lowHr": "Low HR (dark red)", + "gpx.noTiming": "No timing data in this GPX — crop by distance", + "gpx.opacity": "Opacity", + "gpx.opacityShort": "Opac", + "gpx.pace": "Pace", + "gpx.remove": "Remove", + "gpx.removeAria": "Remove track", + "gpx.slow": "Slow (red)", + "gpx.solid": "Solid", + "gpx.strokeWidth": "Stroke width", + "gpx.tipAdjust": "Switch to map and drag anchor pins", + "gpx.tipCrop": "Crop start and end of track", + "gpx.tipRemove": "Remove this track from the project", + "gpx.title": "GPX tracks", + "gpx.toastTapHint": "Tap the track to add pins. Long-press a pin to delete.", + "gpx.trackColour": "Track colour {color}", + "gpx.upload": "Upload", + "gpx.width": "Width", + "language.label": "Language", + "legal.binding": "The English version of this document is the binding text. Translations are provided for convenience.", + "legal.lastUpdated": "Last updated: {date}", + "library.deleteAria": "Delete {name}", + "library.empty": "No saved projects yet. Import a map to start one.", + "library.noMap": "No map yet", + "library.open": "Open", + "library.renameAria": "Rename {name}", + "library.title": "Projects", + "library.updated": "{map} · Updated {date}", + "menu.file": "File", + "menu.legal": "Legal", + "menu.menu": "Menu", + "menu.more": "More actions", + "menu.privacy": "Privacy Policy", + "menu.terms": "Terms of Service", + "notes.centerHint": "Click to center on the map", + "notes.commentPlaceholder": "Add a comment…", + "notes.comments": "Comments", + "notes.commentsHint": "Show inline comments on the map. Drag the right edge of a selected comment to change its width.", + "notes.deleteAria": "Delete annotation {label}", + "notes.displayAria": "Map marker display", + "notes.empty": "No annotations yet. Use the marker tool to add comments to the map.", + "notes.heading": "Annotations", + "notes.labels": "Labels", + "notes.labelsHint": "Show letter labels on map markers", + "notes.removeHint": "Remove this marker", + "notes.toggle": "Notes", + "notes.toggleAria": "Toggle annotations", + "project.untitled": "Untitled project", + "pwa.dismissUpdate": "Dismiss update notice", + "pwa.updateAvailable": "A new version of O-Analysis is available.", + "pwa.updateNow": "Update now", + "rotation.degrees": "{degrees} degrees", + "rotation.group": "Canvas rotation", + "rotation.label": "Rotation", + "rotation.reset": "Reset rotation", + "rotation.resetAria": "Reset rotation to 0 degrees", + "rotation.rotate": "Rotate", + "save.failed": "Autosave failed", + "save.saved": "Saved {time}", + "save.saving": "Saving…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "by Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom}%. Fit map to screen", + "status.fitHint": "Fit the map to the screen", + "status.legal": "Legal", + "status.noCoords": "X: — Y: —", + "status.privacy": "Privacy", + "status.terms": "Terms", + "status.textSize": "Text Size", + "status.textSizeShort": "Text", + "status.tool": "Tool: {tool}", + "status.zoomIn": "Zoom in", + "status.zoomOut": "Zoom out", + "stroke.color": "Color", + "stroke.colorAria": "Color {color}", + "stroke.custom": "Custom color", + "stroke.customHint": "Pick a custom color", + "stroke.opacity": "Opacity", + "stroke.opacityHint": "Transparency for drawn lines and markers", + "stroke.useColor": "Use {color}", + "stroke.width": "Width", + "stroke.widthHint": "Line and marker stroke width", + "style.commentTextSize": "Comment text size", + "tool.eraser": "Eraser", + "tool.eraser.tip": "Click or drag over paths and markers to remove them.", + "tool.eraser.touchTip": "Tap or drag over paths and markers to remove them.", + "tool.gpx": "Adjust GPX", + "tool.gpx.tip": "Click the track to add pins, drag to move them, right-click a pin to delete it.", + "tool.gpx.touchTip": "Tap the track to add pins. Long-press a pin to delete it.", + "tool.gpxNeedsFile": "Import a GPX file first", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Draw", + "tool.line.tip": "Click and drag to draw lines on the map.", + "tool.line.touchTip": "Drag your finger to draw lines on the map.", + "tool.marker": "Marker", + "tool.marker.tip": "Click to place a numbered marker with a comment.", + "tool.marker.touchTip": "Tap to place a numbered marker with a comment.", + "tool.pan": "Pan", + "tool.pan.tip": "Drag the map to move around. Hold Space or middle mouse for temporary pan.", + "tool.pan.touchTip": "Drag with one finger, or use two fingers to pan in any tool.", + "tool.select": "Select", + "tool.select.tip": "Click paths or markers to select them. Press Delete to remove.", + "tool.select.touchTip": "Tap paths or markers to select them.", + "toolbar.annotations": "Annotations", + "toolbar.drawingTools": "Drawing tools", + "toolbar.notes": "Notes", + "toolbar.strokeSettings": "Stroke settings", + "toolbar.style": "Style", + "toolbar.viewAndStroke": "View & stroke settings", + "top.dismissError": "Dismiss error", + "top.editGpxHint": "Change track colours, width, or alignment", + "top.exportDisabled": "Import a map image before exporting", + "top.exportHint": "Preview and download the map with drawings and annotations", + "top.history": "History", + "top.importGpxHint": "Import a GPX file to overlay a route on the map", + "top.importImageHint": "Upload a map image — PNG, JPG, or drop a file on the canvas", + "top.installApp": "Install App", + "top.installHint": "Install O-Analysis as a desktop app", + "top.new": "New", + "top.newHint": "Clear the canvas and start fresh", + "top.open": "Open", + "top.openHint": "Open a saved project ({ext})", + "top.projectName": "Project name", + "top.projects": "Projects", + "top.projectsHint": "Switch between saved projects", + "top.redo": "Redo", + "top.redoHint": "Redo (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Replace the current map image", + "top.save": "Save", + "top.saveDisabled": "Nothing to save yet — import a map or add annotations first", + "top.saveHint": "Save your project as {ext}", + "top.undo": "Undo", + "top.undoHint": "Undo last action (⌘Z / Ctrl+Z)", + "top.updateApp": "Update App", + "top.updateHint": "Reload to use the latest version", + "touch.dismissTips": "Dismiss tips", + "touch.tips": "Pinch to zoom, use two fingers to pan. Tap a tool, then tap the map to draw or place markers." +} diff --git a/src/locales/es.json b/src/locales/es.json new file mode 100644 index 0000000..4a65b9f --- /dev/null +++ b/src/locales/es.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Editar GPX", + "action.editImage": "Editar imagen", + "action.export": "Exportar", + "action.importGpx": "Importar GPX", + "action.importImage": "Importar imagen", + "action.installApp": "Instalar la app", + "action.newProject": "Nuevo proyecto", + "action.openProject": "Abrir proyecto", + "action.projects": "Proyectos", + "action.saveProject": "Guardar proyecto", + "action.updateApp": "Actualizar la app", + "canvas.importHintDrop": "Importa una imagen de mapa para empezar, o suelta un archivo aquí.", + "canvas.importHintTouch": "Importa una imagen de mapa para empezar.", + "canvas.importMap": "Importar mapa", + "canvas.loadingSample": "Cargando ejemplo…", + "canvas.noMap": "No hay ningún mapa cargado", + "canvas.openSample": "Abrir proyecto de ejemplo", + "color.channel": "Canal {channel}", + "color.channelValue": "Valor de {channel}", + "color.custom": "Color personalizado", + "color.hex": "Hex", + "color.hexAria": "Color hexadecimal", + "common.apply": "Aplicar", + "common.cancel": "Cancelar", + "common.close": "Cerrar", + "common.delete": "Eliminar", + "common.dismiss": "Descartar", + "common.errorGeneric": "Algo salió mal", + "common.loading": "Cargando…", + "common.rename": "Cambiar nombre", + "error.body": "O-Analysis encontró un error inesperado. Recargar la página suele solucionarlo.", + "error.damagedEntry": "{label} no válido en el archivo del proyecto (la entrada {index} está dañada)", + "error.decodeImage": "No se pudo leer el archivo de imagen", + "error.decodeMap": "No se pudo leer la imagen del mapa", + "error.exportTooLarge": "El mapa es demasiado grande para exportarlo en este dispositivo", + "error.gpxImport": "No se pudo importar el GPX", + "error.imageImport": "No se pudo importar la imagen", + "error.imageTooLarge": "La imagen es demasiado grande ({size}). El tamaño máximo es de 20 MB.", + "error.invalidEntry": "{label} no válido en el archivo del proyecto", + "error.invalidMap": "Imagen de mapa no válida en el archivo del proyecto", + "error.invalidName": "Nombre de proyecto no válido en el archivo del proyecto", + "error.invalidRotation": "Rotación no válida en el archivo del proyecto", + "error.labelAnnotations": "anotaciones", + "error.labelDrawings": "dibujos", + "error.labelTracks": "recorridos GPX", + "error.loadSample": "No se pudo cargar el proyecto de ejemplo", + "error.noGpxPoints": "El archivo GPX no contiene puntos de recorrido utilizables", + "error.notGpx": "No es un archivo GPX válido", + "error.notImage": "«{name}» no es un archivo de imagen", + "error.notJson": "El archivo del proyecto no es JSON válido", + "error.notOurs": "No es un archivo de proyecto de O-Analysis", + "error.notProjectFile": "No es un archivo de proyecto; se esperaba {ext}", + "error.nothingToSave": "No hay nada que guardar; importa un mapa o añade contenido primero", + "error.openProject": "No se pudo abrir el archivo del proyecto", + "error.projectNotFound": "No se pudo encontrar el proyecto", + "error.readImage": "No se pudo leer el archivo de imagen", + "error.reload": "Recargar página", + "error.renderExport": "No se pudo generar la imagen de exportación", + "error.sampleNotFound": "No se encontró el proyecto de ejemplo", + "error.title": "Algo salió mal", + "error.unsupportedFile": "Tipo de archivo no compatible: «{name}»", + "error.version": "Versión de proyecto no compatible: {version}", + "export.download": "Descargar PNG", + "export.exporting": "Exportando…", + "export.failed": "La exportación falló; inténtalo de nuevo", + "export.fit": "Ajustar", + "export.generating": "Generando vista previa…", + "export.hint": "Desplázate para hacer zoom · arrastra para mover", + "export.includeSidebar": "Incluir panel de anotaciones", + "export.loadingPreview": "Cargando vista previa…", + "export.previewAlt": "Vista previa de la exportación", + "export.resetZoom": "Restablecer zoom", + "export.scaleNotice": "El mapa es más grande de lo que este dispositivo puede exportar a tamaño completo, así que el PNG será del {percent} % de la resolución original.", + "export.sidebarWithComments": "Los comentarios aparecen en el mapa; desactiva el panel para no duplicarlos.", + "export.sidebarWithLabels": "El panel muestra cada letra de marcador con su comentario.", + "export.textSize": "Tamaño del texto", + "export.title": "Exportar", + "export.zoomIn": "Acercar", + "export.zoomOut": "Alejar", + "gpx.adjustMap": "Ajustar en el mapa", + "gpx.applyCrop": "Aplicar recorte", + "gpx.confirm": "Confirmar", + "gpx.crop": "Recortar", + "gpx.cropAria": "Recortar recorrido", + "gpx.cropError": "La selección debe incluir al menos 2 puntos.", + "gpx.cropTitle": "Recortar recorrido", + "gpx.empty": "No hay recorridos GPX cargados.", + "gpx.fast": "Rápido (verde)", + "gpx.highHr": "FC alta (rojo intenso)", + "gpx.hr": "FC", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Estilo de línea", + "gpx.lowHr": "FC baja (rojo oscuro)", + "gpx.noTiming": "Este GPX no tiene datos de tiempo; recorta por distancia", + "gpx.opacity": "Opacidad", + "gpx.opacityShort": "Opac.", + "gpx.pace": "Ritmo", + "gpx.remove": "Quitar", + "gpx.removeAria": "Quitar recorrido", + "gpx.slow": "Lento (rojo)", + "gpx.solid": "Sólido", + "gpx.strokeWidth": "Grosor de línea", + "gpx.tipAdjust": "Cambia al mapa y arrastra los pines de anclaje", + "gpx.tipCrop": "Recorta el inicio y el final del recorrido", + "gpx.tipRemove": "Quita este recorrido del proyecto", + "gpx.title": "Recorridos GPX", + "gpx.toastTapHint": "Toca el recorrido para añadir pines. Mantén pulsado un pin para eliminarlo.", + "gpx.trackColour": "Color del recorrido {color}", + "gpx.upload": "Subir", + "gpx.width": "Ancho", + "language.label": "Idioma", + "legal.binding": "La versión inglesa de este documento es el texto vinculante. Las traducciones se ofrecen solo como referencia.", + "legal.lastUpdated": "Última actualización: {date}", + "library.deleteAria": "Eliminar {name}", + "library.empty": "Todavía no hay proyectos guardados. Importa un mapa para empezar uno.", + "library.noMap": "Sin mapa todavía", + "library.open": "Abierto", + "library.renameAria": "Cambiar el nombre de {name}", + "library.title": "Proyectos", + "library.updated": "{map} · Actualizado el {date}", + "menu.file": "Archivo", + "menu.legal": "Legal", + "menu.menu": "Menú", + "menu.more": "Más acciones", + "menu.privacy": "Política de privacidad", + "menu.terms": "Términos de servicio", + "notes.centerHint": "Haz clic para centrar en el mapa", + "notes.commentPlaceholder": "Añade un comentario…", + "notes.comments": "Comentarios", + "notes.commentsHint": "Muestra comentarios en línea en el mapa. Arrastra el borde derecho de un comentario seleccionado para cambiar su ancho.", + "notes.deleteAria": "Eliminar anotación {label}", + "notes.displayAria": "Visualización de marcadores del mapa", + "notes.empty": "Todavía no hay anotaciones. Usa la herramienta de marcador para añadir comentarios al mapa.", + "notes.heading": "Anotaciones", + "notes.labels": "Etiquetas", + "notes.labelsHint": "Muestra letras en los marcadores del mapa", + "notes.removeHint": "Eliminar este marcador", + "notes.toggle": "Notas", + "notes.toggleAria": "Mostrar u ocultar anotaciones", + "project.untitled": "Proyecto sin título", + "pwa.dismissUpdate": "Descartar el aviso de actualización", + "pwa.updateAvailable": "Hay una nueva versión de O-Analysis disponible.", + "pwa.updateNow": "Actualizar ahora", + "rotation.degrees": "{degrees} grados", + "rotation.group": "Rotación del lienzo", + "rotation.label": "Rotación", + "rotation.reset": "Restablecer rotación", + "rotation.resetAria": "Restablecer la rotación a 0 grados", + "rotation.rotate": "Girar", + "save.failed": "Error al guardar automáticamente", + "save.saved": "Guardado a las {time}", + "save.saving": "Guardando…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "por Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom} %. Ajustar el mapa a la pantalla", + "status.fitHint": "Ajustar el mapa a la pantalla", + "status.legal": "Legal", + "status.noCoords": "X: — Y: —", + "status.privacy": "Privacidad", + "status.terms": "Términos", + "status.textSize": "Tamaño del texto", + "status.textSizeShort": "Texto", + "status.tool": "Herramienta: {tool}", + "status.zoomIn": "Acercar", + "status.zoomOut": "Alejar", + "stroke.color": "Color", + "stroke.colorAria": "Color {color}", + "stroke.custom": "Color personalizado", + "stroke.customHint": "Elige un color personalizado", + "stroke.opacity": "Opacidad", + "stroke.opacityHint": "Transparencia de líneas y marcadores dibujados", + "stroke.useColor": "Usar {color}", + "stroke.width": "Ancho", + "stroke.widthHint": "Grosor de línea y de marcador", + "style.commentTextSize": "Tamaño del texto de los comentarios", + "tool.eraser": "Borrador", + "tool.eraser.tip": "Haz clic o arrastra sobre trazos y marcadores para eliminarlos.", + "tool.eraser.touchTip": "Toca o arrastra sobre trazos y marcadores para eliminarlos.", + "tool.gpx": "Ajustar GPX", + "tool.gpx.tip": "Haz clic en el recorrido para añadir pines, arrástralos para moverlos y haz clic derecho en un pin para eliminarlo.", + "tool.gpx.touchTip": "Toca el recorrido para añadir pines. Mantén pulsado un pin para eliminarlo.", + "tool.gpxNeedsFile": "Importa primero un archivo GPX", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Dibujar", + "tool.line.tip": "Haz clic y arrastra para dibujar líneas en el mapa.", + "tool.line.touchTip": "Arrastra con el dedo para dibujar líneas en el mapa.", + "tool.marker": "Marcador", + "tool.marker.tip": "Haz clic para colocar un marcador numerado con un comentario.", + "tool.marker.touchTip": "Toca para colocar un marcador numerado con un comentario.", + "tool.pan": "Mover", + "tool.pan.tip": "Arrastra el mapa para desplazarte. Mantén pulsada la barra espaciadora o el botón central para moverlo temporalmente.", + "tool.pan.touchTip": "Arrastra con un dedo, o usa dos dedos para mover el mapa en cualquier herramienta.", + "tool.select": "Seleccionar", + "tool.select.tip": "Haz clic en trazos o marcadores para seleccionarlos. Pulsa Supr para eliminar.", + "tool.select.touchTip": "Toca trazos o marcadores para seleccionarlos.", + "toolbar.annotations": "Anotaciones", + "toolbar.drawingTools": "Herramientas de dibujo", + "toolbar.notes": "Notas", + "toolbar.strokeSettings": "Ajustes de trazo", + "toolbar.style": "Estilo", + "toolbar.viewAndStroke": "Vista y ajustes de trazo", + "top.dismissError": "Descartar el error", + "top.editGpxHint": "Cambia el color, el ancho o la alineación del recorrido", + "top.exportDisabled": "Importa una imagen de mapa antes de exportar", + "top.exportHint": "Previsualiza y descarga el mapa con dibujos y anotaciones", + "top.history": "Historial", + "top.importGpxHint": "Importa un archivo GPX para superponer una ruta en el mapa", + "top.importImageHint": "Sube una imagen de mapa (PNG, JPG) o suelta un archivo en el lienzo", + "top.installApp": "Instalar la app", + "top.installHint": "Instala O-Analysis como app de escritorio", + "top.new": "Nuevo", + "top.newHint": "Limpia el lienzo y empieza de nuevo", + "top.open": "Abrir", + "top.openHint": "Abre un proyecto guardado ({ext})", + "top.projectName": "Nombre del proyecto", + "top.projects": "Proyectos", + "top.projectsHint": "Cambia entre proyectos guardados", + "top.redo": "Rehacer", + "top.redoHint": "Rehacer (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Reemplaza la imagen de mapa actual", + "top.save": "Guardar", + "top.saveDisabled": "Todavía no hay nada que guardar; importa un mapa o añade anotaciones primero", + "top.saveHint": "Guarda el proyecto como {ext}", + "top.undo": "Deshacer", + "top.undoHint": "Deshacer la última acción (⌘Z / Ctrl+Z)", + "top.updateApp": "Actualizar la app", + "top.updateHint": "Recarga para usar la versión más reciente", + "touch.dismissTips": "Descartar los consejos", + "touch.tips": "Pellizca para hacer zoom y usa dos dedos para desplazarte. Toca una herramienta y luego el mapa para dibujar o colocar marcadores." +} diff --git a/src/locales/fi.json b/src/locales/fi.json new file mode 100644 index 0000000..79566ba --- /dev/null +++ b/src/locales/fi.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Muokkaa GPX:ää", + "action.editImage": "Muokkaa kuvaa", + "action.export": "Vie", + "action.importGpx": "Tuo GPX", + "action.importImage": "Tuo kuva", + "action.installApp": "Asenna sovellus", + "action.newProject": "Uusi projekti", + "action.openProject": "Avaa projekti", + "action.projects": "Projektit", + "action.saveProject": "Tallenna projekti", + "action.updateApp": "Päivitä sovellus", + "canvas.importHintDrop": "Aloita tuomalla karttakuva tai pudota tiedosto tähän.", + "canvas.importHintTouch": "Aloita tuomalla karttakuva.", + "canvas.importMap": "Tuo kartta", + "canvas.loadingSample": "Ladataan esimerkkiä…", + "canvas.noMap": "Karttaa ei ole ladattu", + "canvas.openSample": "Avaa esimerkkiprojekti", + "color.channel": "{channel}-kanava", + "color.channelValue": "{channel}-arvo", + "color.custom": "Oma väri", + "color.hex": "Hex", + "color.hexAria": "Hex-väri", + "common.apply": "Käytä", + "common.cancel": "Peruuta", + "common.close": "Sulje", + "common.delete": "Poista", + "common.dismiss": "Sulje", + "common.errorGeneric": "Jotain meni pieleen", + "common.loading": "Ladataan…", + "common.rename": "Nimeä uudelleen", + "error.body": "O-Analysis kohtasi odottamattoman virheen. Sivun uudelleenlataus auttaa yleensä.", + "error.damagedEntry": "Virheellinen {label} projektitiedostossa (kohta {index} on vioittunut)", + "error.decodeImage": "Kuvatiedostoa ei voitu lukea", + "error.decodeMap": "Karttakuvaa ei voitu lukea", + "error.exportTooLarge": "Kartta on liian suuri vietäväksi tällä laitteella", + "error.gpxImport": "GPX-tuonti epäonnistui", + "error.imageImport": "Kuvan tuonti epäonnistui", + "error.imageTooLarge": "Kuva on liian suuri ({size}). Enimmäiskoko on 20 Mt.", + "error.invalidEntry": "Virheellinen {label} projektitiedostossa", + "error.invalidMap": "Virheellinen karttakuva projektitiedostossa", + "error.invalidName": "Virheellinen projektin nimi projektitiedostossa", + "error.invalidRotation": "Virheellinen kierto projektitiedostossa", + "error.labelAnnotations": "merkinnät", + "error.labelDrawings": "piirrokset", + "error.labelTracks": "GPX-reitit", + "error.loadSample": "Esimerkkiprojektin lataus epäonnistui", + "error.noGpxPoints": "GPX-tiedostossa ei ole käytettäviä reittipisteitä", + "error.notGpx": "Ei kelvollinen GPX-tiedosto", + "error.notImage": "”{name}” ei ole kuvatiedosto", + "error.notJson": "Projektitiedosto ei ole kelvollista JSONia", + "error.notOurs": "Ei ole O-Analysis-projektitiedosto", + "error.notProjectFile": "Ei projektitiedosto – odotettiin {ext}", + "error.nothingToSave": "Ei mitään tallennettavaa – tuo kartta tai lisää sisältöä ensin", + "error.openProject": "Projektitiedoston avaaminen epäonnistui", + "error.projectNotFound": "Projektia ei löytynyt", + "error.readImage": "Kuvatiedostoa ei voitu lukea", + "error.reload": "Lataa sivu uudelleen", + "error.renderExport": "Vientikuvan piirtäminen epäonnistui", + "error.sampleNotFound": "Esimerkkiprojektia ei löytynyt", + "error.title": "Jotain meni pieleen", + "error.unsupportedFile": "Tiedostotyyppi ei ole tuettu: ”{name}”", + "error.version": "Projektiversio ei ole tuettu: {version}", + "export.download": "Lataa PNG", + "export.exporting": "Viedään…", + "export.failed": "Vienti epäonnistui – yritä uudelleen", + "export.fit": "Sovita", + "export.generating": "Luodaan esikatselua…", + "export.hint": "Zoomaa vierittämällä · siirrä vetämällä", + "export.includeSidebar": "Sisällytä merkintäpaneeli", + "export.loadingPreview": "Ladataan esikatselua…", + "export.previewAlt": "Viennin esikatselu", + "export.resetZoom": "Palauta zoomaus", + "export.scaleNotice": "Kartta on suurempi kuin tämä laite voi viedä täydessä koossa, joten PNG-kuva on {percent} % alkuperäisestä resoluutiosta.", + "export.sidebarWithComments": "Kommentit näkyvät kartalla – poista paneeli käytöstä välttääksesi päällekkäisyyden.", + "export.sidebarWithLabels": "Paneeli listaa jokaisen merkin kirjaimen kommentteineen.", + "export.textSize": "Tekstin koko", + "export.title": "Vie", + "export.zoomIn": "Lähennä", + "export.zoomOut": "Loitonna", + "gpx.adjustMap": "Säädä kartalla", + "gpx.applyCrop": "Käytä rajausta", + "gpx.confirm": "Vahvista", + "gpx.crop": "Rajaa", + "gpx.cropAria": "Rajaa reitti", + "gpx.cropError": "Valinnassa on oltava vähintään 2 pistettä.", + "gpx.cropTitle": "Rajaa reitti", + "gpx.empty": "Ei ladattuja GPX-reittejä.", + "gpx.fast": "Nopea (vihreä)", + "gpx.highHr": "Korkea syke (kirkkaanpunainen)", + "gpx.hr": "Syke", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Viivatyyli", + "gpx.lowHr": "Matala syke (tummanpunainen)", + "gpx.noTiming": "Tässä GPX-tiedostossa ei ole aikatietoja – rajaa matkan mukaan", + "gpx.opacity": "Peittävyys", + "gpx.opacityShort": "Peitt.", + "gpx.pace": "Vauhti", + "gpx.remove": "Poista", + "gpx.removeAria": "Poista reitti", + "gpx.slow": "Hidas (punainen)", + "gpx.solid": "Yhtenäinen", + "gpx.strokeWidth": "Viivan leveys", + "gpx.tipAdjust": "Siirry karttaan ja vedä ankkuripisteitä", + "gpx.tipCrop": "Rajaa reitin alku ja loppu", + "gpx.tipRemove": "Poista tämä reitti projektista", + "gpx.title": "GPX-reitit", + "gpx.toastTapHint": "Napauta reittiä lisätäksesi pisteitä. Poista piste painamalla pitkään.", + "gpx.trackColour": "Reitin väri {color}", + "gpx.upload": "Lataa", + "gpx.width": "Leveys", + "language.label": "Kieli", + "legal.binding": "Asiakirjan englanninkielinen versio on sitova teksti. Käännökset on tarjottu vain tiedoksi.", + "legal.lastUpdated": "Viimeksi päivitetty: {date}", + "library.deleteAria": "Poista {name}", + "library.empty": "Ei vielä tallennettuja projekteja. Aloita tuomalla kartta.", + "library.noMap": "Ei vielä karttaa", + "library.open": "Avoin", + "library.renameAria": "Nimeä {name} uudelleen", + "library.title": "Projektit", + "library.updated": "{map} · Päivitetty {date}", + "menu.file": "Tiedosto", + "menu.legal": "Oikeudellista", + "menu.menu": "Valikko", + "menu.more": "Lisää toimintoja", + "menu.privacy": "Tietosuojaseloste", + "menu.terms": "Käyttöehdot", + "notes.centerHint": "Napsauta keskittääksesi kartalle", + "notes.commentPlaceholder": "Lisää kommentti…", + "notes.comments": "Kommentit", + "notes.commentsHint": "Näytä kommentit suoraan kartalla. Muuta leveyttä vetämällä valitun kommentin oikeaa reunaa.", + "notes.deleteAria": "Poista merkintä {label}", + "notes.displayAria": "Karttamerkkien näyttö", + "notes.empty": "Ei vielä merkintöjä. Lisää kommentteja kartalle merkkityökalulla.", + "notes.heading": "Merkinnät", + "notes.labels": "Tunnisteet", + "notes.labelsHint": "Näytä kirjaintunnisteet karttamerkeissä", + "notes.removeHint": "Poista tämä merkki", + "notes.toggle": "Muistiinpanot", + "notes.toggleAria": "Näytä tai piilota merkinnät", + "project.untitled": "Nimetön projekti", + "pwa.dismissUpdate": "Sulje päivitysilmoitus", + "pwa.updateAvailable": "O-Analysisin uusi versio on saatavilla.", + "pwa.updateNow": "Päivitä nyt", + "rotation.degrees": "{degrees} astetta", + "rotation.group": "Kartan kierto", + "rotation.label": "Kierto", + "rotation.reset": "Palauta kierto", + "rotation.resetAria": "Palauta kierto 0 asteeseen", + "rotation.rotate": "Kierrä", + "save.failed": "Automaattinen tallennus epäonnistui", + "save.saved": "Tallennettu {time}", + "save.saving": "Tallennetaan…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "tekijä Anthony Riley ❤️", + "status.fitAria": "Zoomaus {zoom} %. Sovita kartta näytölle", + "status.fitHint": "Sovita kartta näytölle", + "status.legal": "Oikeudellista", + "status.noCoords": "X: — Y: —", + "status.privacy": "Tietosuoja", + "status.terms": "Ehdot", + "status.textSize": "Tekstin koko", + "status.textSizeShort": "Teksti", + "status.tool": "Työkalu: {tool}", + "status.zoomIn": "Lähennä", + "status.zoomOut": "Loitonna", + "stroke.color": "Väri", + "stroke.colorAria": "Väri {color}", + "stroke.custom": "Oma väri", + "stroke.customHint": "Valitse oma väri", + "stroke.opacity": "Peittävyys", + "stroke.opacityHint": "Piirrettyjen viivojen ja merkkien läpinäkyvyys", + "stroke.useColor": "Käytä väriä {color}", + "stroke.width": "Leveys", + "stroke.widthHint": "Viivan ja merkin leveys", + "style.commentTextSize": "Kommenttien tekstin koko", + "tool.eraser": "Pyyhekumi", + "tool.eraser.tip": "Poista klikkaamalla tai vetämällä viivojen ja merkkien päältä.", + "tool.eraser.touchTip": "Poista napauttamalla tai vetämällä viivojen ja merkkien päältä.", + "tool.gpx": "Säädä GPX", + "tool.gpx.tip": "Lisää pisteitä klikkaamalla reittiä, siirrä niitä vetämällä ja poista piste oikealla napsautuksella.", + "tool.gpx.touchTip": "Lisää pisteitä napauttamalla reittiä. Poista piste painamalla pitkään.", + "tool.gpxNeedsFile": "Tuo ensin GPX-tiedosto", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Piirrä", + "tool.line.tip": "Piirrä viivoja kartalle klikkaamalla ja vetämällä.", + "tool.line.touchTip": "Piirrä viivoja kartalle vetämällä sormella.", + "tool.marker": "Merkki", + "tool.marker.tip": "Klikkaa asettaaksesi numeroidun merkin kommentilla.", + "tool.marker.touchTip": "Napauta asettaaksesi numeroidun merkin kommentilla.", + "tool.pan": "Siirrä", + "tool.pan.tip": "Vedä karttaa liikkuaksesi. Pidä välilyöntiä tai hiiren keskipainiketta pohjassa tilapäiseen siirtoon.", + "tool.pan.touchTip": "Vedä yhdellä sormella tai käytä kahta sormea siirtääksesi kartta millä tahansa työkalulla.", + "tool.select": "Valitse", + "tool.select.tip": "Valitse viivoja tai merkkejä klikkaamalla. Poista painamalla Delete.", + "tool.select.touchTip": "Valitse viivoja tai merkkejä napauttamalla.", + "toolbar.annotations": "Merkinnät", + "toolbar.drawingTools": "Piirrustustyökalut", + "toolbar.notes": "Muistiinpanot", + "toolbar.strokeSettings": "Viivan asetukset", + "toolbar.style": "Tyyli", + "toolbar.viewAndStroke": "Näkymä ja viivan asetukset", + "top.dismissError": "Sulje virheilmoitus", + "top.editGpxHint": "Muuta reitin väriä, leveyttä tai asemointia", + "top.exportDisabled": "Tuo karttakuva ennen viennin aloittamista", + "top.exportHint": "Esikatsele ja lataa kartta piirroksineen ja merkintöineen", + "top.history": "Historia", + "top.importGpxHint": "Tuo GPX-tiedosto reitin piirtämiseksi kartalle", + "top.importImageHint": "Lataa karttakuva – PNG, JPG tai pudota tiedosto piirtoalueelle", + "top.installApp": "Asenna sovellus", + "top.installHint": "Asenna O-Analysis työpöytäsovellukseksi", + "top.new": "Uusi", + "top.newHint": "Tyhjennä piirtoalue ja aloita alusta", + "top.open": "Avaa", + "top.openHint": "Avaa tallennettu projekti ({ext})", + "top.projectName": "Projektin nimi", + "top.projects": "Projektit", + "top.projectsHint": "Vaihda tallennettujen projektien välillä", + "top.redo": "Tee uudelleen", + "top.redoHint": "Tee uudelleen (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Korvaa nykyinen karttakuva", + "top.save": "Tallenna", + "top.saveDisabled": "Ei vielä tallennettavaa – tuo kartta tai lisää merkintöjä ensin", + "top.saveHint": "Tallenna projekti muodossa {ext}", + "top.undo": "Kumoa", + "top.undoHint": "Kumoa viimeisin toiminto (⌘Z / Ctrl+Z)", + "top.updateApp": "Päivitä sovellus", + "top.updateHint": "Lataa uudelleen käyttääksesi uusinta versiota", + "touch.dismissTips": "Sulje vihjeet", + "touch.tips": "Zoomaa nipistämällä, siirrä kahdella sormella. Valitse työkalu napauttamalla ja napauta sitten karttaa piirtääksesi tai asettaaksesi merkkejä." +} diff --git a/src/locales/fr.json b/src/locales/fr.json new file mode 100644 index 0000000..43122e4 --- /dev/null +++ b/src/locales/fr.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Modifier le GPX", + "action.editImage": "Modifier l’image", + "action.export": "Exporter", + "action.importGpx": "Importer un GPX", + "action.importImage": "Importer une image", + "action.installApp": "Installer l’application", + "action.newProject": "Nouveau projet", + "action.openProject": "Ouvrir un projet", + "action.projects": "Projets", + "action.saveProject": "Enregistrer le projet", + "action.updateApp": "Mettre à jour l’application", + "canvas.importHintDrop": "Importez une image de carte pour commencer, ou déposez un fichier ici.", + "canvas.importHintTouch": "Importez une image de carte pour commencer.", + "canvas.importMap": "Importer une carte", + "canvas.loadingSample": "Chargement de l’exemple…", + "canvas.noMap": "Aucune carte chargée", + "canvas.openSample": "Ouvrir le projet d’exemple", + "color.channel": "Canal {channel}", + "color.channelValue": "Valeur {channel}", + "color.custom": "Couleur personnalisée", + "color.hex": "Hex", + "color.hexAria": "Couleur hexadécimale", + "common.apply": "Appliquer", + "common.cancel": "Annuler", + "common.close": "Fermer", + "common.delete": "Supprimer", + "common.dismiss": "Ignorer", + "common.errorGeneric": "Une erreur s’est produite", + "common.loading": "Chargement…", + "common.rename": "Renommer", + "error.body": "O-Analysis a rencontré une erreur inattendue. Recharger la page règle généralement le problème.", + "error.damagedEntry": "{label} invalide dans le fichier du projet (l’entrée {index} est endommagée)", + "error.decodeImage": "Impossible de lire le fichier image", + "error.decodeMap": "Impossible de lire l’image de la carte", + "error.exportTooLarge": "La carte est trop grande pour être exportée sur cet appareil", + "error.gpxImport": "Échec de l’importation du GPX", + "error.imageImport": "Échec de l’importation de l’image", + "error.imageTooLarge": "L’image est trop volumineuse ({size}). La taille maximale est de 20 Mo.", + "error.invalidEntry": "{label} invalide dans le fichier du projet", + "error.invalidMap": "Image de carte invalide dans le fichier du projet", + "error.invalidName": "Nom de projet invalide dans le fichier du projet", + "error.invalidRotation": "Rotation invalide dans le fichier du projet", + "error.labelAnnotations": "annotations", + "error.labelDrawings": "tracés", + "error.labelTracks": "traces GPX", + "error.loadSample": "Échec du chargement du projet d’exemple", + "error.noGpxPoints": "Le fichier GPX ne contient aucun point de trace exploitable", + "error.notGpx": "Fichier GPX invalide", + "error.notImage": "« {name} » n’est pas un fichier image", + "error.notJson": "Le fichier du projet n’est pas un JSON valide", + "error.notOurs": "Ce n’est pas un fichier de projet O-Analysis", + "error.notProjectFile": "Ce n’est pas un fichier de projet ; {ext} attendu", + "error.nothingToSave": "Rien à enregistrer : importez d’abord une carte ou ajoutez du contenu", + "error.openProject": "Impossible d’ouvrir le fichier du projet", + "error.projectNotFound": "Projet introuvable", + "error.readImage": "Impossible de lire le fichier image", + "error.reload": "Recharger la page", + "error.renderExport": "Impossible de générer l’image d’export", + "error.sampleNotFound": "Projet d’exemple introuvable", + "error.title": "Une erreur s’est produite", + "error.unsupportedFile": "Type de fichier non pris en charge : « {name} »", + "error.version": "Version de projet non prise en charge : {version}", + "export.download": "Télécharger le PNG", + "export.exporting": "Export en cours…", + "export.failed": "L’export a échoué, veuillez réessayer", + "export.fit": "Ajuster", + "export.generating": "Génération de l’aperçu…", + "export.hint": "Faites défiler pour zoomer · glissez pour déplacer", + "export.includeSidebar": "Inclure le panneau des annotations", + "export.loadingPreview": "Chargement de l’aperçu…", + "export.previewAlt": "Aperçu de l’export", + "export.resetZoom": "Réinitialiser le zoom", + "export.scaleNotice": "La carte est plus grande que ce que cet appareil peut exporter en taille réelle ; le PNG sera donc à {percent} % de la résolution d’origine.", + "export.sidebarWithComments": "Les commentaires sont affichés sur la carte ; désactivez le panneau pour éviter les doublons.", + "export.sidebarWithLabels": "Le panneau liste chaque lettre de repère avec son commentaire.", + "export.textSize": "Taille du texte", + "export.title": "Exporter", + "export.zoomIn": "Zoom avant", + "export.zoomOut": "Zoom arrière", + "gpx.adjustMap": "Ajuster sur la carte", + "gpx.applyCrop": "Appliquer le recadrage", + "gpx.confirm": "Confirmer", + "gpx.crop": "Recadrer", + "gpx.cropAria": "Recadrer la trace", + "gpx.cropError": "La sélection doit comporter au moins 2 points.", + "gpx.cropTitle": "Recadrer la trace", + "gpx.empty": "Aucune trace GPX chargée.", + "gpx.fast": "Rapide (vert)", + "gpx.highHr": "FC élevée (rouge vif)", + "gpx.hr": "FC", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Style de trait", + "gpx.lowHr": "FC basse (rouge foncé)", + "gpx.noTiming": "Ce GPX ne contient pas de données de temps : recadrez par distance", + "gpx.opacity": "Opacité", + "gpx.opacityShort": "Opac.", + "gpx.pace": "Allure", + "gpx.remove": "Retirer", + "gpx.removeAria": "Retirer la trace", + "gpx.slow": "Lent (rouge)", + "gpx.solid": "Plein", + "gpx.strokeWidth": "Épaisseur du trait", + "gpx.tipAdjust": "Passer à la carte et faire glisser les points d’ancrage", + "gpx.tipCrop": "Recadrer le début et la fin de la trace", + "gpx.tipRemove": "Retirer cette trace du projet", + "gpx.title": "Traces GPX", + "gpx.toastTapHint": "Touchez la trace pour ajouter des épingles. Appuyez longuement sur une épingle pour la supprimer.", + "gpx.trackColour": "Couleur de la trace {color}", + "gpx.upload": "Ajouter", + "gpx.width": "Largeur", + "language.label": "Langue", + "legal.binding": "La version anglaise de ce document fait foi. Les traductions sont fournies à titre indicatif.", + "legal.lastUpdated": "Dernière mise à jour : {date}", + "library.deleteAria": "Supprimer {name}", + "library.empty": "Aucun projet enregistré pour le moment. Importez une carte pour en commencer un.", + "library.noMap": "Pas encore de carte", + "library.open": "Ouvert", + "library.renameAria": "Renommer {name}", + "library.title": "Projets", + "library.updated": "{map} · Mis à jour le {date}", + "menu.file": "Fichier", + "menu.legal": "Mentions légales", + "menu.menu": "Menu", + "menu.more": "Plus d’actions", + "menu.privacy": "Politique de confidentialité", + "menu.terms": "Conditions d’utilisation", + "notes.centerHint": "Cliquez pour centrer sur la carte", + "notes.commentPlaceholder": "Ajouter un commentaire…", + "notes.comments": "Commentaires", + "notes.commentsHint": "Affiche les commentaires directement sur la carte. Faites glisser le bord droit d’un commentaire sélectionné pour changer sa largeur.", + "notes.deleteAria": "Supprimer l’annotation {label}", + "notes.displayAria": "Affichage des repères de la carte", + "notes.empty": "Aucune annotation pour le moment. Utilisez l’outil repère pour ajouter des commentaires sur la carte.", + "notes.heading": "Annotations", + "notes.labels": "Étiquettes", + "notes.labelsHint": "Affiche les lettres sur les repères de la carte", + "notes.removeHint": "Supprimer ce repère", + "notes.toggle": "Notes", + "notes.toggleAria": "Afficher ou masquer les annotations", + "project.untitled": "Projet sans titre", + "pwa.dismissUpdate": "Ignorer la notification de mise à jour", + "pwa.updateAvailable": "Une nouvelle version d’O-Analysis est disponible.", + "pwa.updateNow": "Mettre à jour", + "rotation.degrees": "{degrees} degrés", + "rotation.group": "Rotation de la carte", + "rotation.label": "Rotation", + "rotation.reset": "Réinitialiser la rotation", + "rotation.resetAria": "Réinitialiser la rotation à 0 degré", + "rotation.rotate": "Pivoter", + "save.failed": "Échec de l’enregistrement automatique", + "save.saved": "Enregistré à {time}", + "save.saving": "Enregistrement…", + "status.coords": "X : {x} Y : {y}", + "status.credit": "par Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom} %. Ajuster la carte à l’écran", + "status.fitHint": "Ajuster la carte à l’écran", + "status.legal": "Mentions légales", + "status.noCoords": "X : — Y : —", + "status.privacy": "Confidentialité", + "status.terms": "Conditions", + "status.textSize": "Taille du texte", + "status.textSizeShort": "Texte", + "status.tool": "Outil : {tool}", + "status.zoomIn": "Zoom avant", + "status.zoomOut": "Zoom arrière", + "stroke.color": "Couleur", + "stroke.colorAria": "Couleur {color}", + "stroke.custom": "Couleur personnalisée", + "stroke.customHint": "Choisir une couleur personnalisée", + "stroke.opacity": "Opacité", + "stroke.opacityHint": "Transparence des traits et repères dessinés", + "stroke.useColor": "Utiliser {color}", + "stroke.width": "Largeur", + "stroke.widthHint": "Épaisseur du trait et du repère", + "style.commentTextSize": "Taille du texte des commentaires", + "tool.eraser": "Gomme", + "tool.eraser.tip": "Cliquez ou glissez sur les tracés et repères pour les supprimer.", + "tool.eraser.touchTip": "Touchez ou glissez sur les tracés et repères pour les supprimer.", + "tool.gpx": "Ajuster le GPX", + "tool.gpx.tip": "Cliquez sur la trace pour ajouter des épingles, glissez-les pour les déplacer, et faites un clic droit sur une épingle pour la supprimer.", + "tool.gpx.touchTip": "Touchez la trace pour ajouter des épingles. Appuyez longuement sur une épingle pour la supprimer.", + "tool.gpxNeedsFile": "Importez d’abord un fichier GPX", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Tracer", + "tool.line.tip": "Cliquez et glissez pour tracer des lignes sur la carte.", + "tool.line.touchTip": "Faites glisser le doigt pour tracer des lignes sur la carte.", + "tool.marker": "Repère", + "tool.marker.tip": "Cliquez pour placer un repère numéroté avec un commentaire.", + "tool.marker.touchTip": "Touchez pour placer un repère numéroté avec un commentaire.", + "tool.pan": "Déplacer", + "tool.pan.tip": "Faites glisser la carte pour vous déplacer. Maintenez la barre d’espace ou le bouton central pour un déplacement temporaire.", + "tool.pan.touchTip": "Glissez avec un doigt, ou utilisez deux doigts pour déplacer la carte avec n’importe quel outil.", + "tool.select": "Sélectionner", + "tool.select.tip": "Cliquez sur des tracés ou des repères pour les sélectionner. Appuyez sur Suppr pour les retirer.", + "tool.select.touchTip": "Touchez des tracés ou des repères pour les sélectionner.", + "toolbar.annotations": "Annotations", + "toolbar.drawingTools": "Outils de dessin", + "toolbar.notes": "Notes", + "toolbar.strokeSettings": "Réglages du trait", + "toolbar.style": "Style", + "toolbar.viewAndStroke": "Affichage et réglages du trait", + "top.dismissError": "Ignorer l’erreur", + "top.editGpxHint": "Modifier la couleur, la largeur ou l’alignement des traces", + "top.exportDisabled": "Importez une image de carte avant d’exporter", + "top.exportHint": "Prévisualiser et télécharger la carte avec les tracés et annotations", + "top.history": "Historique", + "top.importGpxHint": "Importer un fichier GPX pour superposer un itinéraire sur la carte", + "top.importImageHint": "Charger une image de carte (PNG, JPG) ou déposer un fichier sur le canevas", + "top.installApp": "Installer l’application", + "top.installHint": "Installer O-Analysis comme application de bureau", + "top.new": "Nouveau", + "top.newHint": "Vider le canevas et recommencer", + "top.open": "Ouvrir", + "top.openHint": "Ouvrir un projet enregistré ({ext})", + "top.projectName": "Nom du projet", + "top.projects": "Projets", + "top.projectsHint": "Passer d’un projet enregistré à un autre", + "top.redo": "Rétablir", + "top.redoHint": "Rétablir (⌘⇧Z / Ctrl+Maj+Z)", + "top.replaceImageHint": "Remplacer l’image de carte actuelle", + "top.save": "Enregistrer", + "top.saveDisabled": "Rien à enregistrer pour le moment : importez une carte ou ajoutez des annotations", + "top.saveHint": "Enregistrer le projet au format {ext}", + "top.undo": "Annuler", + "top.undoHint": "Annuler la dernière action (⌘Z / Ctrl+Z)", + "top.updateApp": "Mettre à jour l’application", + "top.updateHint": "Recharger pour utiliser la dernière version", + "touch.dismissTips": "Ignorer les conseils", + "touch.tips": "Pincez pour zoomer, utilisez deux doigts pour déplacer la carte. Touchez un outil puis la carte pour tracer ou placer des repères." +} diff --git a/src/locales/nb.json b/src/locales/nb.json new file mode 100644 index 0000000..96217a3 --- /dev/null +++ b/src/locales/nb.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Rediger GPX", + "action.editImage": "Rediger bilde", + "action.export": "Eksporter", + "action.importGpx": "Importer GPX", + "action.importImage": "Importer bilde", + "action.installApp": "Installer appen", + "action.newProject": "Nytt prosjekt", + "action.openProject": "Åpne prosjekt", + "action.projects": "Prosjekter", + "action.saveProject": "Lagre prosjekt", + "action.updateApp": "Oppdater appen", + "canvas.importHintDrop": "Importer et kartbilde for å komme i gang, eller slipp en fil her.", + "canvas.importHintTouch": "Importer et kartbilde for å komme i gang.", + "canvas.importMap": "Importer kart", + "canvas.loadingSample": "Laster eksempel…", + "canvas.noMap": "Ingen kart lastet", + "canvas.openSample": "Åpne eksempelprosjekt", + "color.channel": "{channel}-kanal", + "color.channelValue": "{channel}-verdi", + "color.custom": "Egendefinert farge", + "color.hex": "Hex", + "color.hexAria": "Hex-farge", + "common.apply": "Bruk", + "common.cancel": "Avbryt", + "common.close": "Lukk", + "common.delete": "Slett", + "common.dismiss": "Lukk", + "common.errorGeneric": "Noe gikk galt", + "common.loading": "Laster…", + "common.rename": "Gi nytt navn", + "error.body": "O-Analysis møtte en uventet feil. Å laste siden på nytt pleier å løse problemet.", + "error.damagedEntry": "Ugyldig {label} i prosjektfilen (oppføring {index} er skadet)", + "error.decodeImage": "Bildefilen kunne ikke leses", + "error.decodeMap": "Kartbildet kunne ikke leses", + "error.exportTooLarge": "Kartet er for stort til å eksporteres på denne enheten", + "error.gpxImport": "GPX-importen mislyktes", + "error.imageImport": "Bildeimporten mislyktes", + "error.imageTooLarge": "Bildet er for stort ({size}). Maksimal størrelse er 20 MB.", + "error.invalidEntry": "Ugyldig {label} i prosjektfilen", + "error.invalidMap": "Ugyldig kartbilde i prosjektfilen", + "error.invalidName": "Ugyldig prosjektnavn i prosjektfilen", + "error.invalidRotation": "Ugyldig rotasjon i prosjektfilen", + "error.labelAnnotations": "merknader", + "error.labelDrawings": "tegninger", + "error.labelTracks": "GPX-spor", + "error.loadSample": "Eksempelprosjektet kunne ikke lastes", + "error.noGpxPoints": "GPX-filen inneholder ingen brukbare sporpunkter", + "error.notGpx": "Ikke en gyldig GPX-fil", + "error.notImage": "«{name}» er ikke en bildefil", + "error.notJson": "Prosjektfilen er ikke gyldig JSON", + "error.notOurs": "Ikke en O-Analysis-prosjektfil", + "error.notProjectFile": "Ikke en prosjektfil – forventet {ext}", + "error.nothingToSave": "Ingenting å lagre – importer et kart eller legg til innhold først", + "error.openProject": "Prosjektfilen kunne ikke åpnes", + "error.projectNotFound": "Fant ikke prosjektet", + "error.readImage": "Bildefilen kunne ikke leses", + "error.reload": "Last siden på nytt", + "error.renderExport": "Eksportbildet kunne ikke tegnes", + "error.sampleNotFound": "Fant ikke eksempelprosjektet", + "error.title": "Noe gikk galt", + "error.unsupportedFile": "Filtypen støttes ikke: «{name}»", + "error.version": "Prosjektversjonen støttes ikke: {version}", + "export.download": "Last ned PNG", + "export.exporting": "Eksporterer…", + "export.failed": "Eksporten mislyktes – prøv igjen", + "export.fit": "Tilpass", + "export.generating": "Lager forhåndsvisning…", + "export.hint": "Rull for å zoome · dra for å flytte", + "export.includeSidebar": "Ta med merknadspanelet", + "export.loadingPreview": "Laster forhåndsvisning…", + "export.previewAlt": "Forhåndsvisning av eksport", + "export.resetZoom": "Tilbakestill zoom", + "export.scaleNotice": "Kartet er større enn det denne enheten kan eksportere i full størrelse, så PNG-filen blir {percent} % av opprinnelig oppløsning.", + "export.sidebarWithComments": "Kommentarene vises på kartet – slå av panelet for å unngå duplisering.", + "export.sidebarWithLabels": "Panelet lister opp hver markørbokstav med kommentaren.", + "export.textSize": "Tekststørrelse", + "export.title": "Eksporter", + "export.zoomIn": "Zoom inn", + "export.zoomOut": "Zoom ut", + "gpx.adjustMap": "Juster på kartet", + "gpx.applyCrop": "Bruk beskjæring", + "gpx.confirm": "Bekreft", + "gpx.crop": "Beskjær", + "gpx.cropAria": "Beskjær spor", + "gpx.cropError": "Utvalget må inneholde minst 2 punkter.", + "gpx.cropTitle": "Beskjær spor", + "gpx.empty": "Ingen GPX-spor lastet.", + "gpx.fast": "Raskt (grønn)", + "gpx.highHr": "Høy puls (lys rød)", + "gpx.hr": "Puls", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Linjestil", + "gpx.lowHr": "Lav puls (mørk rød)", + "gpx.noTiming": "Ingen tidsdata i denne GPX-filen – beskjær etter distanse", + "gpx.opacity": "Opasitet", + "gpx.opacityShort": "Opas.", + "gpx.pace": "Tempo", + "gpx.remove": "Fjern", + "gpx.removeAria": "Fjern spor", + "gpx.slow": "Sakte (rød)", + "gpx.solid": "Heltrukket", + "gpx.strokeWidth": "Linjebredde", + "gpx.tipAdjust": "Bytt til kartet og dra ankerpunktene", + "gpx.tipCrop": "Beskjær starten og slutten av sporet", + "gpx.tipRemove": "Fjern dette sporet fra prosjektet", + "gpx.title": "GPX-spor", + "gpx.toastTapHint": "Trykk på sporet for å legge til punkter. Trykk lenge på et punkt for å slette det.", + "gpx.trackColour": "Sporfarge {color}", + "gpx.upload": "Last opp", + "gpx.width": "Bredde", + "language.label": "Språk", + "legal.binding": "Den engelske versjonen av dokumentet er den bindende teksten. Oversettelsene er laget for din bekvemmelighet.", + "legal.lastUpdated": "Sist oppdatert: {date}", + "library.deleteAria": "Slett {name}", + "library.empty": "Ingen lagrede prosjekter ennå. Importer et kart for å starte ett.", + "library.noMap": "Ingen kart ennå", + "library.open": "Åpne", + "library.renameAria": "Gi nytt navn til {name}", + "library.title": "Prosjekter", + "library.updated": "{map} · Oppdatert {date}", + "menu.file": "Fil", + "menu.legal": "Juridisk", + "menu.menu": "Meny", + "menu.more": "Flere handlinger", + "menu.privacy": "Personvernerklæring", + "menu.terms": "Vilkår for bruk", + "notes.centerHint": "Klikk for å sentrere på kartet", + "notes.commentPlaceholder": "Legg til en kommentar…", + "notes.comments": "Kommentarer", + "notes.commentsHint": "Vis kommentarer direkte på kartet. Dra i høyre kant på en valgt kommentar for å endre bredden.", + "notes.deleteAria": "Slett merknad {label}", + "notes.displayAria": "Visning av kartmarkører", + "notes.empty": "Ingen merknader ennå. Bruk markørverktøyet for å legge til kommentarer på kartet.", + "notes.heading": "Merknader", + "notes.labels": "Etiketter", + "notes.labelsHint": "Vis bokstavetiketter på kartmarkørene", + "notes.removeHint": "Fjern denne markøren", + "notes.toggle": "Notater", + "notes.toggleAria": "Vis eller skjul merknader", + "project.untitled": "Uten navn", + "pwa.dismissUpdate": "Lukk oppdateringsvarselet", + "pwa.updateAvailable": "En ny versjon av O-Analysis er tilgjengelig.", + "pwa.updateNow": "Oppdater nå", + "rotation.degrees": "{degrees} grader", + "rotation.group": "Kartrotasjon", + "rotation.label": "Rotasjon", + "rotation.reset": "Tilbakestill rotasjon", + "rotation.resetAria": "Tilbakestill rotasjonen til 0 grader", + "rotation.rotate": "Roter", + "save.failed": "Automatisk lagring mislyktes", + "save.saved": "Lagret {time}", + "save.saving": "Lagrer…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "av Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom} %. Tilpass kartet til skjermen", + "status.fitHint": "Tilpass kartet til skjermen", + "status.legal": "Juridisk", + "status.noCoords": "X: — Y: —", + "status.privacy": "Personvern", + "status.terms": "Vilkår", + "status.textSize": "Tekststørrelse", + "status.textSizeShort": "Tekst", + "status.tool": "Verktøy: {tool}", + "status.zoomIn": "Zoom inn", + "status.zoomOut": "Zoom ut", + "stroke.color": "Farge", + "stroke.colorAria": "Farge {color}", + "stroke.custom": "Egendefinert farge", + "stroke.customHint": "Velg en egendefinert farge", + "stroke.opacity": "Opasitet", + "stroke.opacityHint": "Gjennomsiktighet for tegnede linjer og markører", + "stroke.useColor": "Bruk {color}", + "stroke.width": "Bredde", + "stroke.widthHint": "Linje- og markørbredde", + "style.commentTextSize": "Tekststørrelse for kommentarer", + "tool.eraser": "Viskelær", + "tool.eraser.tip": "Klikk eller dra over linjer og markører for å fjerne dem.", + "tool.eraser.touchTip": "Trykk eller dra over linjer og markører for å fjerne dem.", + "tool.gpx": "Juster GPX", + "tool.gpx.tip": "Klikk på sporet for å legge til punkter, dra for å flytte dem, og høyreklikk et punkt for å slette det.", + "tool.gpx.touchTip": "Trykk på sporet for å legge til punkter. Trykk lenge på et punkt for å slette det.", + "tool.gpxNeedsFile": "Importer en GPX-fil først", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Tegn", + "tool.line.tip": "Klikk og dra for å tegne linjer på kartet.", + "tool.line.touchTip": "Dra fingeren for å tegne linjer på kartet.", + "tool.marker": "Markør", + "tool.marker.tip": "Klikk for å plassere en nummerert markør med en kommentar.", + "tool.marker.touchTip": "Trykk for å plassere en nummerert markør med en kommentar.", + "tool.pan": "Flytt", + "tool.pan.tip": "Dra kartet for å bevege deg rundt. Hold mellomrom eller midtknappen for midlertidig flytting.", + "tool.pan.touchTip": "Dra med ett finger, eller bruk to fingre for å flytte i alle verktøy.", + "tool.select": "Velg", + "tool.select.tip": "Klikk på linjer eller markører for å velge dem. Trykk Delete for å fjerne.", + "tool.select.touchTip": "Trykk på linjer eller markører for å velge dem.", + "toolbar.annotations": "Merknader", + "toolbar.drawingTools": "Tegneverktøy", + "toolbar.notes": "Notater", + "toolbar.strokeSettings": "Linjeinnstillinger", + "toolbar.style": "Stil", + "toolbar.viewAndStroke": "Visning og linjeinnstillinger", + "top.dismissError": "Lukk feilmeldingen", + "top.editGpxHint": "Endre sporfarge, bredde eller justering", + "top.exportDisabled": "Importer et kartbilde før du eksporterer", + "top.exportHint": "Forhåndsvis og last ned kartet med tegninger og merknader", + "top.history": "Historikk", + "top.importGpxHint": "Importer en GPX-fil for å legge en rute over kartet", + "top.importImageHint": "Last opp et kartbilde – PNG, JPG, eller slipp en fil på lerretet", + "top.installApp": "Installer appen", + "top.installHint": "Installer O-Analysis som et skrivebordsprogram", + "top.new": "Ny", + "top.newHint": "Tøm lerretet og start på nytt", + "top.open": "Åpne", + "top.openHint": "Åpne et lagret prosjekt ({ext})", + "top.projectName": "Prosjektnavn", + "top.projects": "Prosjekter", + "top.projectsHint": "Bytt mellom lagrede prosjekter", + "top.redo": "Gjør om", + "top.redoHint": "Gjør om (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Erstatt det gjeldende kartbildet", + "top.save": "Lagre", + "top.saveDisabled": "Ingenting å lagre ennå – importer et kart eller legg til merknader først", + "top.saveHint": "Lagre prosjektet som {ext}", + "top.undo": "Angre", + "top.undoHint": "Angre siste handling (⌘Z / Ctrl+Z)", + "top.updateApp": "Oppdater appen", + "top.updateHint": "Last inn på nytt for å bruke den nyeste versjonen", + "touch.dismissTips": "Lukk tipsene", + "touch.tips": "Klyp for å zoome, bruk to fingre for å flytte. Trykk på et verktøy og deretter på kartet for å tegne eller plassere markører." +} diff --git a/src/locales/pt-BR.json b/src/locales/pt-BR.json new file mode 100644 index 0000000..1287d20 --- /dev/null +++ b/src/locales/pt-BR.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Editar GPX", + "action.editImage": "Editar imagem", + "action.export": "Exportar", + "action.importGpx": "Importar GPX", + "action.importImage": "Importar imagem", + "action.installApp": "Instalar o app", + "action.newProject": "Novo projeto", + "action.openProject": "Abrir projeto", + "action.projects": "Projetos", + "action.saveProject": "Salvar projeto", + "action.updateApp": "Atualizar o app", + "canvas.importHintDrop": "Importe uma imagem de mapa para começar, ou solte um arquivo aqui.", + "canvas.importHintTouch": "Importe uma imagem de mapa para começar.", + "canvas.importMap": "Importar mapa", + "canvas.loadingSample": "Carregando exemplo…", + "canvas.noMap": "Nenhum mapa carregado", + "canvas.openSample": "Abrir projeto de exemplo", + "color.channel": "Canal {channel}", + "color.channelValue": "Valor de {channel}", + "color.custom": "Cor personalizada", + "color.hex": "Hex", + "color.hexAria": "Cor hexadecimal", + "common.apply": "Aplicar", + "common.cancel": "Cancelar", + "common.close": "Fechar", + "common.delete": "Excluir", + "common.dismiss": "Dispensar", + "common.errorGeneric": "Algo deu errado", + "common.loading": "Carregando…", + "common.rename": "Renomear", + "error.body": "O O-Analysis encontrou um erro inesperado. Recarregar a página geralmente resolve.", + "error.damagedEntry": "{label} inválido no arquivo do projeto (o item {index} está danificado)", + "error.decodeImage": "Não foi possível ler o arquivo de imagem", + "error.decodeMap": "Não foi possível ler a imagem do mapa", + "error.exportTooLarge": "O mapa é grande demais para exportar neste dispositivo", + "error.gpxImport": "Falha ao importar o GPX", + "error.imageImport": "Falha ao importar a imagem", + "error.imageTooLarge": "A imagem é grande demais ({size}). O tamanho máximo é 20 MB.", + "error.invalidEntry": "{label} inválido no arquivo do projeto", + "error.invalidMap": "Imagem de mapa inválida no arquivo do projeto", + "error.invalidName": "Nome de projeto inválido no arquivo do projeto", + "error.invalidRotation": "Rotação inválida no arquivo do projeto", + "error.labelAnnotations": "anotações", + "error.labelDrawings": "desenhos", + "error.labelTracks": "trajetos GPX", + "error.loadSample": "Falha ao carregar o projeto de exemplo", + "error.noGpxPoints": "O arquivo GPX não contém pontos de trajeto utilizáveis", + "error.notGpx": "Não é um arquivo GPX válido", + "error.notImage": "“{name}” não é um arquivo de imagem", + "error.notJson": "O arquivo do projeto não é um JSON válido", + "error.notOurs": "Não é um arquivo de projeto do O-Analysis", + "error.notProjectFile": "Não é um arquivo de projeto; esperado {ext}", + "error.nothingToSave": "Nada para salvar – importe um mapa ou adicione conteúdo primeiro", + "error.openProject": "Não foi possível abrir o arquivo do projeto", + "error.projectNotFound": "Não foi possível encontrar o projeto", + "error.readImage": "Não foi possível ler o arquivo de imagem", + "error.reload": "Recarregar página", + "error.renderExport": "Não foi possível gerar a imagem da exportação", + "error.sampleNotFound": "Projeto de exemplo não encontrado", + "error.title": "Algo deu errado", + "error.unsupportedFile": "Tipo de arquivo não suportado: “{name}”", + "error.version": "Versão de projeto não suportada: {version}", + "export.download": "Baixar PNG", + "export.exporting": "Exportando…", + "export.failed": "A exportação falhou – tente novamente", + "export.fit": "Ajustar", + "export.generating": "Gerando pré-visualização…", + "export.hint": "Role para dar zoom · arraste para mover", + "export.includeSidebar": "Incluir painel de anotações", + "export.loadingPreview": "Carregando pré-visualização…", + "export.previewAlt": "Pré-visualização da exportação", + "export.resetZoom": "Redefinir zoom", + "export.scaleNotice": "O mapa é maior do que este dispositivo consegue exportar em tamanho real, então o PNG ficará com {percent} % da resolução original.", + "export.sidebarWithComments": "Os comentários aparecem no mapa – desative o painel para não duplicar.", + "export.sidebarWithLabels": "O painel lista cada letra de marcador com o seu comentário.", + "export.textSize": "Tamanho do texto", + "export.title": "Exportar", + "export.zoomIn": "Aproximar", + "export.zoomOut": "Afastar", + "gpx.adjustMap": "Ajustar no mapa", + "gpx.applyCrop": "Aplicar recorte", + "gpx.confirm": "Confirmar", + "gpx.crop": "Recortar", + "gpx.cropAria": "Recortar trajeto", + "gpx.cropError": "A seleção deve incluir pelo menos 2 pontos.", + "gpx.cropTitle": "Recortar trajeto", + "gpx.empty": "Nenhum trajeto GPX carregado.", + "gpx.fast": "Rápido (verde)", + "gpx.highHr": "FC alta (vermelho vivo)", + "gpx.hr": "FC", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Estilo de linha", + "gpx.lowHr": "FC baixa (vermelho escuro)", + "gpx.noTiming": "Este GPX não tem dados de tempo – recorte por distância", + "gpx.opacity": "Opacidade", + "gpx.opacityShort": "Opac.", + "gpx.pace": "Ritmo", + "gpx.remove": "Remover", + "gpx.removeAria": "Remover trajeto", + "gpx.slow": "Lento (vermelho)", + "gpx.solid": "Sólido", + "gpx.strokeWidth": "Espessura da linha", + "gpx.tipAdjust": "Mude para o mapa e arraste os pinos de ancoragem", + "gpx.tipCrop": "Recorte o início e o fim do trajeto", + "gpx.tipRemove": "Remover este trajeto do projeto", + "gpx.title": "Trajetos GPX", + "gpx.toastTapHint": "Toque no trajeto para adicionar pinos. Pressione e segure um pino para excluí-lo.", + "gpx.trackColour": "Cor do trajeto {color}", + "gpx.upload": "Enviar", + "gpx.width": "Largura", + "language.label": "Idioma", + "legal.binding": "A versão em inglês deste documento é o texto vinculante. As traduções são oferecidas apenas para sua conveniência.", + "legal.lastUpdated": "Última atualização: {date}", + "library.deleteAria": "Excluir {name}", + "library.empty": "Nenhum projeto salvo ainda. Importe um mapa para começar um.", + "library.noMap": "Sem mapa ainda", + "library.open": "Aberto", + "library.renameAria": "Renomear {name}", + "library.title": "Projetos", + "library.updated": "{map} · Atualizado em {date}", + "menu.file": "Arquivo", + "menu.legal": "Jurídico", + "menu.menu": "Menu", + "menu.more": "Mais ações", + "menu.privacy": "Política de privacidade", + "menu.terms": "Termos de uso", + "notes.centerHint": "Clique para centralizar no mapa", + "notes.commentPlaceholder": "Adicione um comentário…", + "notes.comments": "Comentários", + "notes.commentsHint": "Mostra comentários diretamente no mapa. Arraste a borda direita de um comentário selecionado para mudar a largura.", + "notes.deleteAria": "Excluir anotação {label}", + "notes.displayAria": "Exibição dos marcadores do mapa", + "notes.empty": "Nenhuma anotação ainda. Use a ferramenta de marcador para adicionar comentários ao mapa.", + "notes.heading": "Anotações", + "notes.labels": "Rótulos", + "notes.labelsHint": "Mostra letras nos marcadores do mapa", + "notes.removeHint": "Remover este marcador", + "notes.toggle": "Notas", + "notes.toggleAria": "Mostrar ou ocultar anotações", + "project.untitled": "Projeto sem título", + "pwa.dismissUpdate": "Dispensar aviso de atualização", + "pwa.updateAvailable": "Uma nova versão do O-Analysis está disponível.", + "pwa.updateNow": "Atualizar agora", + "rotation.degrees": "{degrees} graus", + "rotation.group": "Rotação da tela", + "rotation.label": "Rotação", + "rotation.reset": "Redefinir rotação", + "rotation.resetAria": "Redefinir a rotação para 0 graus", + "rotation.rotate": "Girar", + "save.failed": "Falha no salvamento automático", + "save.saved": "Salvo às {time}", + "save.saving": "Salvando…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "por Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom} %. Ajustar o mapa à tela", + "status.fitHint": "Ajustar o mapa à tela", + "status.legal": "Jurídico", + "status.noCoords": "X: — Y: —", + "status.privacy": "Privacidade", + "status.terms": "Termos", + "status.textSize": "Tamanho do texto", + "status.textSizeShort": "Texto", + "status.tool": "Ferramenta: {tool}", + "status.zoomIn": "Aproximar", + "status.zoomOut": "Afastar", + "stroke.color": "Cor", + "stroke.colorAria": "Cor {color}", + "stroke.custom": "Cor personalizada", + "stroke.customHint": "Escolher uma cor personalizada", + "stroke.opacity": "Opacidade", + "stroke.opacityHint": "Transparência das linhas e marcadores desenhados", + "stroke.useColor": "Usar {color}", + "stroke.width": "Largura", + "stroke.widthHint": "Espessura da linha e do marcador", + "style.commentTextSize": "Tamanho do texto dos comentários", + "tool.eraser": "Borracha", + "tool.eraser.tip": "Clique ou arraste sobre trajetos e marcadores para removê-los.", + "tool.eraser.touchTip": "Toque ou arraste sobre trajetos e marcadores para removê-los.", + "tool.gpx": "Ajustar GPX", + "tool.gpx.tip": "Clique no trajeto para adicionar pinos, arraste-os para mover e clique com o botão direito em um pino para excluí-lo.", + "tool.gpx.touchTip": "Toque no trajeto para adicionar pinos. Pressione e segure um pino para excluí-lo.", + "tool.gpxNeedsFile": "Importe um arquivo GPX primeiro", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Desenhar", + "tool.line.tip": "Clique e arraste para desenhar linhas no mapa.", + "tool.line.touchTip": "Arraste o dedo para desenhar linhas no mapa.", + "tool.marker": "Marcador", + "tool.marker.tip": "Clique para colocar um marcador numerado com um comentário.", + "tool.marker.touchTip": "Toque para colocar um marcador numerado com um comentário.", + "tool.pan": "Mover", + "tool.pan.tip": "Arraste o mapa para se movimentar. Segure a barra de espaço ou o botão do meio para mover temporariamente.", + "tool.pan.touchTip": "Arraste com um dedo, ou use dois dedos para mover o mapa em qualquer ferramenta.", + "tool.select": "Selecionar", + "tool.select.tip": "Clique em trajetos ou marcadores para selecioná-los. Pressione Delete para remover.", + "tool.select.touchTip": "Toque em trajetos ou marcadores para selecioná-los.", + "toolbar.annotations": "Anotações", + "toolbar.drawingTools": "Ferramentas de desenho", + "toolbar.notes": "Notas", + "toolbar.strokeSettings": "Configurações de traço", + "toolbar.style": "Estilo", + "toolbar.viewAndStroke": "Visualização e configurações de traço", + "top.dismissError": "Dispensar erro", + "top.editGpxHint": "Mude a cor, a largura ou o alinhamento dos trajetos", + "top.exportDisabled": "Importe uma imagem de mapa antes de exportar", + "top.exportHint": "Pré-visualize e baixe o mapa com desenhos e anotações", + "top.history": "Histórico", + "top.importGpxHint": "Importe um arquivo GPX para sobrepor uma rota no mapa", + "top.importImageHint": "Envie uma imagem de mapa (PNG, JPG) ou solte um arquivo na tela", + "top.installApp": "Instalar o app", + "top.installHint": "Instale o O-Analysis como aplicativo de desktop", + "top.new": "Novo", + "top.newHint": "Limpe a tela e comece do zero", + "top.open": "Abrir", + "top.openHint": "Abra um projeto salvo ({ext})", + "top.projectName": "Nome do projeto", + "top.projects": "Projetos", + "top.projectsHint": "Alterne entre projetos salvos", + "top.redo": "Refazer", + "top.redoHint": "Refazer (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Substituir a imagem de mapa atual", + "top.save": "Salvar", + "top.saveDisabled": "Nada para salvar ainda – importe um mapa ou adicione anotações primeiro", + "top.saveHint": "Salvar o projeto como {ext}", + "top.undo": "Desfazer", + "top.undoHint": "Desfazer a última ação (⌘Z / Ctrl+Z)", + "top.updateApp": "Atualizar o app", + "top.updateHint": "Recarregue para usar a versão mais recente", + "touch.dismissTips": "Dispensar as dicas", + "touch.tips": "Pince para dar zoom, use dois dedos para mover. Toque em uma ferramenta e depois no mapa para desenhar ou colocar marcadores." +} diff --git a/src/locales/pt-PT.json b/src/locales/pt-PT.json new file mode 100644 index 0000000..5c6a144 --- /dev/null +++ b/src/locales/pt-PT.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Editar GPX", + "action.editImage": "Editar imagem", + "action.export": "Exportar", + "action.importGpx": "Importar GPX", + "action.importImage": "Importar imagem", + "action.installApp": "Instalar a aplicação", + "action.newProject": "Novo projeto", + "action.openProject": "Abrir projeto", + "action.projects": "Projetos", + "action.saveProject": "Guardar projeto", + "action.updateApp": "Atualizar a aplicação", + "canvas.importHintDrop": "Importe uma imagem de mapa para começar, ou arraste um ficheiro para aqui.", + "canvas.importHintTouch": "Importe uma imagem de mapa para começar.", + "canvas.importMap": "Importar mapa", + "canvas.loadingSample": "A carregar o exemplo…", + "canvas.noMap": "Nenhum mapa carregado", + "canvas.openSample": "Abrir projeto de exemplo", + "color.channel": "Canal {channel}", + "color.channelValue": "Valor de {channel}", + "color.custom": "Cor personalizada", + "color.hex": "Hex", + "color.hexAria": "Cor hexadecimal", + "common.apply": "Aplicar", + "common.cancel": "Cancelar", + "common.close": "Fechar", + "common.delete": "Eliminar", + "common.dismiss": "Dispensar", + "common.errorGeneric": "Algo correu mal", + "common.loading": "A carregar…", + "common.rename": "Mudar o nome", + "error.body": "O O-Analysis encontrou um erro inesperado. Recarregar a página costuma resolver.", + "error.damagedEntry": "{label} inválido no ficheiro do projeto (o item {index} está danificado)", + "error.decodeImage": "Não foi possível ler o ficheiro de imagem", + "error.decodeMap": "Não foi possível ler a imagem do mapa", + "error.exportTooLarge": "O mapa é demasiado grande para exportar neste dispositivo", + "error.gpxImport": "Falha ao importar o GPX", + "error.imageImport": "Falha ao importar a imagem", + "error.imageTooLarge": "A imagem é demasiado grande ({size}). O tamanho máximo é 20 MB.", + "error.invalidEntry": "{label} inválido no ficheiro do projeto", + "error.invalidMap": "Imagem de mapa inválida no ficheiro do projeto", + "error.invalidName": "Nome de projeto inválido no ficheiro do projeto", + "error.invalidRotation": "Rotação inválida no ficheiro do projeto", + "error.labelAnnotations": "anotações", + "error.labelDrawings": "desenhos", + "error.labelTracks": "percursos GPX", + "error.loadSample": "Falha ao carregar o projeto de exemplo", + "error.noGpxPoints": "O ficheiro GPX não contém pontos de percurso utilizáveis", + "error.notGpx": "Não é um ficheiro GPX válido", + "error.notImage": "«{name}» não é um ficheiro de imagem", + "error.notJson": "O ficheiro do projeto não é um JSON válido", + "error.notOurs": "Não é um ficheiro de projeto do O-Analysis", + "error.notProjectFile": "Não é um ficheiro de projeto; esperado {ext}", + "error.nothingToSave": "Nada para guardar – importe um mapa ou adicione conteúdo primeiro", + "error.openProject": "Não foi possível abrir o ficheiro do projeto", + "error.projectNotFound": "Não foi possível encontrar o projeto", + "error.readImage": "Não foi possível ler o ficheiro de imagem", + "error.reload": "Recarregar página", + "error.renderExport": "Não foi possível gerar a imagem da exportação", + "error.sampleNotFound": "Projeto de exemplo não encontrado", + "error.title": "Algo correu mal", + "error.unsupportedFile": "Tipo de ficheiro não suportado: «{name}»", + "error.version": "Versão de projeto não suportada: {version}", + "export.download": "Transferir PNG", + "export.exporting": "A exportar…", + "export.failed": "A exportação falhou – tente novamente", + "export.fit": "Ajustar", + "export.generating": "A gerar pré-visualização…", + "export.hint": "Deslize para ampliar · arraste para mover", + "export.includeSidebar": "Incluir painel de anotações", + "export.loadingPreview": "A carregar pré-visualização…", + "export.previewAlt": "Pré-visualização da exportação", + "export.resetZoom": "Repor zoom", + "export.scaleNotice": "O mapa é maior do que este dispositivo consegue exportar em tamanho real, por isso o PNG ficará com {percent} % da resolução original.", + "export.sidebarWithComments": "Os comentários aparecem no mapa – desative o painel para não duplicar.", + "export.sidebarWithLabels": "O painel lista cada letra de marcador com o respetivo comentário.", + "export.textSize": "Tamanho do texto", + "export.title": "Exportar", + "export.zoomIn": "Ampliar", + "export.zoomOut": "Reduzir", + "gpx.adjustMap": "Ajustar no mapa", + "gpx.applyCrop": "Aplicar recorte", + "gpx.confirm": "Confirmar", + "gpx.crop": "Recortar", + "gpx.cropAria": "Recortar percurso", + "gpx.cropError": "A seleção deve incluir pelo menos 2 pontos.", + "gpx.cropTitle": "Recortar percurso", + "gpx.empty": "Nenhum percurso GPX carregado.", + "gpx.fast": "Rápido (verde)", + "gpx.highHr": "FC alta (vermelho vivo)", + "gpx.hr": "FC", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Estilo de linha", + "gpx.lowHr": "FC baixa (vermelho escuro)", + "gpx.noTiming": "Este GPX não tem dados de tempo – recorte por distância", + "gpx.opacity": "Opacidade", + "gpx.opacityShort": "Opac.", + "gpx.pace": "Ritmo", + "gpx.remove": "Remover", + "gpx.removeAria": "Remover percurso", + "gpx.slow": "Lento (vermelho)", + "gpx.solid": "Contínuo", + "gpx.strokeWidth": "Espessura da linha", + "gpx.tipAdjust": "Mude para o mapa e arraste os alfinetes de ancoragem", + "gpx.tipCrop": "Recorte o início e o fim do percurso", + "gpx.tipRemove": "Remover este percurso do projeto", + "gpx.title": "Percursos GPX", + "gpx.toastTapHint": "Toque no percurso para adicionar alfinetes. Mantenha premido um alfinete para o eliminar.", + "gpx.trackColour": "Cor do percurso {color}", + "gpx.upload": "Carregar", + "gpx.width": "Largura", + "language.label": "Idioma", + "legal.binding": "A versão inglesa deste documento é o texto vinculativo. As traduções são disponibilizadas apenas para conveniência.", + "legal.lastUpdated": "Última atualização: {date}", + "library.deleteAria": "Eliminar {name}", + "library.empty": "Ainda não há projetos guardados. Importe um mapa para começar um.", + "library.noMap": "Sem mapa ainda", + "library.open": "Aberto", + "library.renameAria": "Mudar o nome de {name}", + "library.title": "Projetos", + "library.updated": "{map} · Atualizado em {date}", + "menu.file": "Ficheiro", + "menu.legal": "Jurídico", + "menu.menu": "Menu", + "menu.more": "Mais ações", + "menu.privacy": "Política de privacidade", + "menu.terms": "Termos de utilização", + "notes.centerHint": "Clique para centrar no mapa", + "notes.commentPlaceholder": "Adicione um comentário…", + "notes.comments": "Comentários", + "notes.commentsHint": "Mostra os comentários diretamente no mapa. Arraste a borda direita de um comentário selecionado para alterar a largura.", + "notes.deleteAria": "Eliminar anotação {label}", + "notes.displayAria": "Visualização dos marcadores do mapa", + "notes.empty": "Ainda não há anotações. Use a ferramenta de marcador para adicionar comentários ao mapa.", + "notes.heading": "Anotações", + "notes.labels": "Etiquetas", + "notes.labelsHint": "Mostra letras nos marcadores do mapa", + "notes.removeHint": "Remover este marcador", + "notes.toggle": "Notas", + "notes.toggleAria": "Mostrar ou ocultar anotações", + "project.untitled": "Projeto sem título", + "pwa.dismissUpdate": "Dispensar aviso de atualização", + "pwa.updateAvailable": "Está disponível uma nova versão do O-Analysis.", + "pwa.updateNow": "Atualizar agora", + "rotation.degrees": "{degrees} graus", + "rotation.group": "Rotação do ecrã", + "rotation.label": "Rotação", + "rotation.reset": "Repor rotação", + "rotation.resetAria": "Repor a rotação para 0 graus", + "rotation.rotate": "Rodar", + "save.failed": "Falha na gravação automática", + "save.saved": "Guardado às {time}", + "save.saving": "A guardar…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "por Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom} %. Ajustar o mapa ao ecrã", + "status.fitHint": "Ajustar o mapa ao ecrã", + "status.legal": "Jurídico", + "status.noCoords": "X: — Y: —", + "status.privacy": "Privacidade", + "status.terms": "Termos", + "status.textSize": "Tamanho do texto", + "status.textSizeShort": "Texto", + "status.tool": "Ferramenta: {tool}", + "status.zoomIn": "Ampliar", + "status.zoomOut": "Reduzir", + "stroke.color": "Cor", + "stroke.colorAria": "Cor {color}", + "stroke.custom": "Cor personalizada", + "stroke.customHint": "Escolher uma cor personalizada", + "stroke.opacity": "Opacidade", + "stroke.opacityHint": "Transparência das linhas e marcadores desenhados", + "stroke.useColor": "Usar {color}", + "stroke.width": "Largura", + "stroke.widthHint": "Espessura da linha e do marcador", + "style.commentTextSize": "Tamanho do texto dos comentários", + "tool.eraser": "Borracha", + "tool.eraser.tip": "Clique ou arraste sobre percursos e marcadores para os remover.", + "tool.eraser.touchTip": "Toque ou arraste sobre percursos e marcadores para os remover.", + "tool.gpx": "Ajustar GPX", + "tool.gpx.tip": "Clique no percurso para adicionar alfinetes, arraste-os para os mover e clique com o botão direito num alfinete para o eliminar.", + "tool.gpx.touchTip": "Toque no percurso para adicionar alfinetes. Mantenha premido um alfinete para o eliminar.", + "tool.gpxNeedsFile": "Importe primeiro um ficheiro GPX", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Desenhar", + "tool.line.tip": "Clique e arraste para desenhar linhas no mapa.", + "tool.line.touchTip": "Arraste o dedo para desenhar linhas no mapa.", + "tool.marker": "Marcador", + "tool.marker.tip": "Clique para colocar um marcador numerado com um comentário.", + "tool.marker.touchTip": "Toque para colocar um marcador numerado com um comentário.", + "tool.pan": "Mover", + "tool.pan.tip": "Arraste o mapa para se deslocar. Mantenha premida a barra de espaço ou o botão do meio para mover temporariamente.", + "tool.pan.touchTip": "Arraste com um dedo, ou use dois dedos para mover o mapa em qualquer ferramenta.", + "tool.select": "Selecionar", + "tool.select.tip": "Clique em percursos ou marcadores para os selecionar. Prima Delete para remover.", + "tool.select.touchTip": "Toque em percursos ou marcadores para os selecionar.", + "toolbar.annotations": "Anotações", + "toolbar.drawingTools": "Ferramentas de desenho", + "toolbar.notes": "Notas", + "toolbar.strokeSettings": "Definições do traço", + "toolbar.style": "Estilo", + "toolbar.viewAndStroke": "Visualização e definições do traço", + "top.dismissError": "Dispensar erro", + "top.editGpxHint": "Altere a cor, a largura ou o alinhamento dos percursos", + "top.exportDisabled": "Importe uma imagem de mapa antes de exportar", + "top.exportHint": "Pré-visualize e transfira o mapa com desenhos e anotações", + "top.history": "Histórico", + "top.importGpxHint": "Importe um ficheiro GPX para sobrepor um percurso no mapa", + "top.importImageHint": "Carregue uma imagem de mapa (PNG, JPG) ou arraste um ficheiro para a área de desenho", + "top.installApp": "Instalar a aplicação", + "top.installHint": "Instale o O-Analysis como aplicação de ambiente de trabalho", + "top.new": "Novo", + "top.newHint": "Limpe a área de desenho e comece de novo", + "top.open": "Abrir", + "top.openHint": "Abra um projeto guardado ({ext})", + "top.projectName": "Nome do projeto", + "top.projects": "Projetos", + "top.projectsHint": "Alterne entre projetos guardados", + "top.redo": "Refazer", + "top.redoHint": "Refazer (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Substituir a imagem de mapa atual", + "top.save": "Guardar", + "top.saveDisabled": "Nada para guardar ainda – importe um mapa ou adicione anotações primeiro", + "top.saveHint": "Guardar o projeto como {ext}", + "top.undo": "Anular", + "top.undoHint": "Anular a última ação (⌘Z / Ctrl+Z)", + "top.updateApp": "Atualizar a aplicação", + "top.updateHint": "Recarregue para usar a versão mais recente", + "touch.dismissTips": "Dispensar as sugestões", + "touch.tips": "Faça um pinça para ampliar, use dois dedos para mover. Toque numa ferramenta e depois no mapa para desenhar ou colocar marcadores." +} diff --git a/src/locales/sv.json b/src/locales/sv.json new file mode 100644 index 0000000..e710480 --- /dev/null +++ b/src/locales/sv.json @@ -0,0 +1,232 @@ +{ + "action.editGpx": "Redigera GPX", + "action.editImage": "Redigera bild", + "action.export": "Exportera", + "action.importGpx": "Importera GPX", + "action.importImage": "Importera bild", + "action.installApp": "Installera appen", + "action.newProject": "Nytt projekt", + "action.openProject": "Öppna projekt", + "action.projects": "Projekt", + "action.saveProject": "Spara projekt", + "action.updateApp": "Uppdatera appen", + "canvas.importHintDrop": "Importera en kartbild för att komma igång, eller släpp en fil här.", + "canvas.importHintTouch": "Importera en kartbild för att komma igång.", + "canvas.importMap": "Importera karta", + "canvas.loadingSample": "Läser in exempel…", + "canvas.noMap": "Ingen karta inläst", + "canvas.openSample": "Öppna exempelprojekt", + "color.channel": "{channel}-kanal", + "color.channelValue": "{channel}-värde", + "color.custom": "Egen färg", + "color.hex": "Hex", + "color.hexAria": "Hex-färg", + "common.apply": "Använd", + "common.cancel": "Avbryt", + "common.close": "Stäng", + "common.delete": "Ta bort", + "common.dismiss": "Stäng", + "common.errorGeneric": "Något gick fel", + "common.loading": "Laddar…", + "common.rename": "Byt namn", + "error.body": "O-Analysis stötte på ett oväntat fel. Att ladda om sidan brukar lösa det.", + "error.damagedEntry": "Ogiltig {label} i projektfilen (post {index} är skadad)", + "error.decodeImage": "Bildfilen kunde inte läsas in", + "error.decodeMap": "Kartbilden kunde inte läsas in", + "error.exportTooLarge": "Kartan är för stor för att exporteras på den här enheten", + "error.gpxImport": "GPX-importen misslyckades", + "error.imageImport": "Bildimporten misslyckades", + "error.imageTooLarge": "Bilden är för stor ({size}). Maxstorleken är 20 MB.", + "error.invalidEntry": "Ogiltig {label} i projektfilen", + "error.invalidMap": "Ogiltig kartbild i projektfilen", + "error.invalidName": "Ogiltigt projektnamn i projektfilen", + "error.invalidRotation": "Ogiltig rotation i projektfilen", + "error.labelAnnotations": "annoteringar", + "error.labelDrawings": "ritningar", + "error.labelTracks": "GPX-spår", + "error.loadSample": "Exempelprojektet kunde inte läsas in", + "error.noGpxPoints": "GPX-filen innehåller inga användbara spårpunkter", + "error.notGpx": "Inte en giltig GPX-fil", + "error.notImage": "”{name}” är inte en bildfil", + "error.notJson": "Projektfilen är inte giltig JSON", + "error.notOurs": "Inte en O-Analysis-projektfil", + "error.notProjectFile": "Inte en projektfil – förväntade {ext}", + "error.nothingToSave": "Inget att spara – importera en karta eller lägg till innehåll först", + "error.openProject": "Projektfilen kunde inte öppnas", + "error.projectNotFound": "Projektet kunde inte hittas", + "error.readImage": "Bildfilen kunde inte läsas", + "error.reload": "Ladda om sidan", + "error.renderExport": "Exportbilden kunde inte renderas", + "error.sampleNotFound": "Exempelprojektet hittades inte", + "error.title": "Något gick fel", + "error.unsupportedFile": "Filtypen stöds inte: ”{name}”", + "error.version": "Projektversionen stöds inte: {version}", + "export.download": "Ladda ner PNG", + "export.exporting": "Exporterar…", + "export.failed": "Exporten misslyckades – försök igen", + "export.fit": "Anpassa", + "export.generating": "Skapar förhandsvisning…", + "export.hint": "Scrolla för att zooma · dra för att panorera", + "export.includeSidebar": "Inkludera annoteringspanel", + "export.loadingPreview": "Läser in förhandsvisning…", + "export.previewAlt": "Förhandsvisning av export", + "export.resetZoom": "Återställ zoom", + "export.scaleNotice": "Kartan är större än vad den här enheten kan exportera i full storlek, så PNG-filen blir {percent} % av originalupplösningen.", + "export.sidebarWithComments": "Kommentarerna visas på kartan – stäng av panelen för att undvika dubbletter.", + "export.sidebarWithLabels": "Panelen listar varje markörbokstav med sin kommentar.", + "export.textSize": "Textstorlek", + "export.title": "Exportera", + "export.zoomIn": "Zooma in", + "export.zoomOut": "Zooma ut", + "gpx.adjustMap": "Justera på kartan", + "gpx.applyCrop": "Tillämpa beskärning", + "gpx.confirm": "Bekräfta", + "gpx.crop": "Beskär", + "gpx.cropAria": "Beskär spår", + "gpx.cropError": "Markeringen måste innehålla minst 2 punkter.", + "gpx.cropTitle": "Beskär spår", + "gpx.empty": "Inga GPX-spår inlästa.", + "gpx.fast": "Snabb (grön)", + "gpx.highHr": "Hög puls (ljusröd)", + "gpx.hr": "Puls", + "gpx.km": "{value} km", + "gpx.lineStyleAria": "Linjestil", + "gpx.lowHr": "Låg puls (mörkröd)", + "gpx.noTiming": "Ingen tidsdata i den här GPX-filen – beskär efter sträcka", + "gpx.opacity": "Opacitet", + "gpx.opacityShort": "Opak.", + "gpx.pace": "Tempo", + "gpx.remove": "Ta bort", + "gpx.removeAria": "Ta bort spår", + "gpx.slow": "Långsam (röd)", + "gpx.solid": "Heldragen", + "gpx.strokeWidth": "Linjebredd", + "gpx.tipAdjust": "Byt till kartan och dra ankarpunkterna", + "gpx.tipCrop": "Beskär spårets början och slut", + "gpx.tipRemove": "Ta bort spåret från projektet", + "gpx.title": "GPX-spår", + "gpx.toastTapHint": "Tryck på spåret för att lägga till punkter. Tryck länge på en punkt för att ta bort den.", + "gpx.trackColour": "Spårfärg {color}", + "gpx.upload": "Ladda upp", + "gpx.width": "Bredd", + "language.label": "Språk", + "legal.binding": "Den engelska versionen av dokumentet är den bindande texten. Översättningarna tillhandahålls för bekvämlighets skull.", + "legal.lastUpdated": "Senast uppdaterad: {date}", + "library.deleteAria": "Ta bort {name}", + "library.empty": "Inga sparade projekt än. Importera en karta för att börja.", + "library.noMap": "Ingen karta än", + "library.open": "Öppna", + "library.renameAria": "Byt namn på {name}", + "library.title": "Projekt", + "library.updated": "{map} · Uppdaterad {date}", + "menu.file": "Fil", + "menu.legal": "Juridiskt", + "menu.menu": "Meny", + "menu.more": "Fler åtgärder", + "menu.privacy": "Integritetspolicy", + "menu.terms": "Användarvillkor", + "notes.centerHint": "Klicka för att centrera på kartan", + "notes.commentPlaceholder": "Lägg till en kommentar…", + "notes.comments": "Kommentarer", + "notes.commentsHint": "Visa kommentarer direkt på kartan. Dra i den högra kanten på en vald kommentar för att ändra bredden.", + "notes.deleteAria": "Ta bort annotering {label}", + "notes.displayAria": "Visning av kartmarkörer", + "notes.empty": "Inga annoteringar än. Använd markörverktyget för att lägga till kommentarer på kartan.", + "notes.heading": "Annoteringar", + "notes.labels": "Etiketter", + "notes.labelsHint": "Visa bokstavsetiketter på kartmarkörerna", + "notes.removeHint": "Ta bort den här markören", + "notes.toggle": "Noteringar", + "notes.toggleAria": "Visa eller dölj annoteringar", + "project.untitled": "Namnlöst projekt", + "pwa.dismissUpdate": "Stäng uppdateringsmeddelandet", + "pwa.updateAvailable": "En ny version av O-Analysis finns tillgänglig.", + "pwa.updateNow": "Uppdatera nu", + "rotation.degrees": "{degrees} grader", + "rotation.group": "Kartrotation", + "rotation.label": "Rotation", + "rotation.reset": "Återställ rotation", + "rotation.resetAria": "Återställ rotationen till 0 grader", + "rotation.rotate": "Rotera", + "save.failed": "Automatisk sparning misslyckades", + "save.saved": "Sparad {time}", + "save.saving": "Sparar…", + "status.coords": "X: {x} Y: {y}", + "status.credit": "av Anthony Riley ❤️", + "status.fitAria": "Zoom {zoom} %. Anpassa kartan till skärmen", + "status.fitHint": "Anpassa kartan till skärmen", + "status.legal": "Juridiskt", + "status.noCoords": "X: — Y: —", + "status.privacy": "Integritet", + "status.terms": "Villkor", + "status.textSize": "Textstorlek", + "status.textSizeShort": "Text", + "status.tool": "Verktyg: {tool}", + "status.zoomIn": "Zooma in", + "status.zoomOut": "Zooma ut", + "stroke.color": "Färg", + "stroke.colorAria": "Färg {color}", + "stroke.custom": "Egen färg", + "stroke.customHint": "Välj en egen färg", + "stroke.opacity": "Opacitet", + "stroke.opacityHint": "Genomskinlighet för ritade linjer och markörer", + "stroke.useColor": "Använd {color}", + "stroke.width": "Bredd", + "stroke.widthHint": "Linje- och markörbredd", + "style.commentTextSize": "Textstorlek för kommentarer", + "tool.eraser": "Sudd", + "tool.eraser.tip": "Klicka eller dra över linjer och markörer för att ta bort dem.", + "tool.eraser.touchTip": "Tryck eller dra över linjer och markörer för att ta bort dem.", + "tool.gpx": "Justera GPX", + "tool.gpx.tip": "Klicka på spåret för att lägga till punkter, dra för att flytta dem och högerklicka på en punkt för att ta bort den.", + "tool.gpx.touchTip": "Tryck på spåret för att lägga till punkter. Tryck länge på en punkt för att ta bort den.", + "tool.gpxNeedsFile": "Importera en GPX-fil först", + "tool.gpxShort": "GPX", + "tool.hotkeyLabel": "{tip} ({key})", + "tool.line": "Rita", + "tool.line.tip": "Klicka och dra för att rita linjer på kartan.", + "tool.line.touchTip": "Dra med fingret för att rita linjer på kartan.", + "tool.marker": "Markör", + "tool.marker.tip": "Klicka för att placera en numrerad markör med en kommentar.", + "tool.marker.touchTip": "Tryck för att placera en numrerad markör med en kommentar.", + "tool.pan": "Panorera", + "tool.pan.tip": "Dra kartan för att flytta runt. Håll ned mellanslag eller mittenknappen för tillfällig panorering.", + "tool.pan.touchTip": "Dra med ett finger, eller använd två fingrar för att panorera i alla verktyg.", + "tool.select": "Markera", + "tool.select.tip": "Klicka på linjer eller markörer för att markera dem. Tryck på Delete för att ta bort.", + "tool.select.touchTip": "Tryck på linjer eller markörer för att markera dem.", + "toolbar.annotations": "Annoteringar", + "toolbar.drawingTools": "Ritverktyg", + "toolbar.notes": "Noteringar", + "toolbar.strokeSettings": "Linjeinställningar", + "toolbar.style": "Stil", + "toolbar.viewAndStroke": "Vy och linjeinställningar", + "top.dismissError": "Stäng felmeddelandet", + "top.editGpxHint": "Ändra spårfärg, bredd eller justering", + "top.exportDisabled": "Importera en kartbild innan du exporterar", + "top.exportHint": "Förhandsgranska och ladda ner kartan med ritningar och annoteringar", + "top.history": "Historik", + "top.importGpxHint": "Importera en GPX-fil för att lägga en rutt över kartan", + "top.importImageHint": "Ladda upp en kartbild – PNG, JPG eller släpp en fil på duken", + "top.installApp": "Installera appen", + "top.installHint": "Installera O-Analysis som en skrivbordsapp", + "top.new": "Nytt", + "top.newHint": "Rensa duken och börja om", + "top.open": "Öppna", + "top.openHint": "Öppna ett sparat projekt ({ext})", + "top.projectName": "Projektnamn", + "top.projects": "Projekt", + "top.projectsHint": "Byt mellan sparade projekt", + "top.redo": "Gör om", + "top.redoHint": "Gör om (⌘⇧Z / Ctrl+Shift+Z)", + "top.replaceImageHint": "Ersätt den aktuella kartbilden", + "top.save": "Spara", + "top.saveDisabled": "Inget att spara ännu – importera en karta eller lägg till annoteringar först", + "top.saveHint": "Spara projektet som {ext}", + "top.undo": "Ångra", + "top.undoHint": "Ångra senaste åtgärden (⌘Z / Ctrl+Z)", + "top.updateApp": "Uppdatera appen", + "top.updateHint": "Ladda om för att använda den senaste versionen", + "touch.dismissTips": "Stäng tipsen", + "touch.tips": "Nyp för att zooma, använd två fingrar för att panorera. Tryck på ett verktyg och sedan på kartan för att rita eller placera markörer." +} diff --git a/tsconfig.app.json b/tsconfig.app.json index 6830b6f..345ba12 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -6,6 +6,7 @@ "module": "esnext", "types": ["vite/client"], "allowArbitraryExtensions": true, + "resolveJsonModule": true, "skipLibCheck": true, /* Bundler mode */ From d382449771653d5a67ef95e32d42d4a7fd2f92a7 Mon Sep 17 00:00:00 2001 From: Anthony Riley Date: Fri, 16 Oct 2026 14:47:56 +0200 Subject: [PATCH 2/3] feat: translate the interface and add a language picker Moves user-facing text out of components and utilities into the dictionary: toolbars, menus, dialogs, tooltips, error messages, the exported sidebar heading and the project library. The language picker sits in the status bar and in the touch Style sheet. Language packs are excluded from the service worker precache and cached on first use. Exported PNGs keep the system font stack, which covers the accented Latin letters these languages use. Also: text wrapping breaks words wider than a column (long Finnish compounds), the tool palette is wide enough for the longest labels, and the touch Style sheet keeps the comment text size label clear of its slider. Co-Authored-By: Claude Haiku 5.5 --- src/App.css | 12 +++++ src/components/AnnotationsPanel.tsx | 35 +++++++------ src/components/AppErrorBoundary.tsx | 10 ++-- src/components/BottomSheet.tsx | 4 +- src/components/BottomToolBar.tsx | 22 ++++---- src/components/CanvasArea.tsx | 12 ++--- src/components/CanvasRotationControl.tsx | 12 +++-- src/components/ColorPickerModal.tsx | 18 ++++--- src/components/EditGpxModal.tsx | 58 +++++++++++---------- src/components/ExportModal.tsx | 41 +++++++-------- src/components/GpxCropPanel.tsx | 16 +++--- src/components/LanguageSelect.tsx | 22 ++++++++ src/components/LegalModal.tsx | 7 ++- src/components/ProjectLibraryModal.tsx | 45 ++++++++-------- src/components/PwaUpdateBanner.tsx | 8 +-- src/components/SlideDrawer.tsx | 4 +- src/components/StatusBar.tsx | 37 ++++++++------ src/components/StrokeSettings.tsx | 28 +++++----- src/components/ToastBanner.tsx | 4 +- src/components/ToolPalette.tsx | 10 ++-- src/components/TopBar.tsx | 65 +++++++++++++----------- src/components/TopBarMenu.tsx | 16 +++--- src/components/TouchHints.tsx | 9 ++-- src/components/overflowActions.tsx | 19 +++---- src/config/tools.ts | 63 ++++++++++++----------- src/index.css | 31 +++++++++++ src/main.tsx | 18 ++++--- src/persistence/library.ts | 7 +++ src/persistence/projectStorage.ts | 3 +- src/projectLibrary.ts | 3 +- src/utils/export.ts | 34 ++----------- src/utils/files.ts | 15 +++--- src/utils/gpx.ts | 5 +- src/utils/markerComments.ts | 28 +--------- src/utils/project.ts | 11 ++-- src/utils/projectValidation.ts | 23 +++++---- src/utils/textWrap.test.ts | 34 +++++++++++++ src/utils/textWrap.ts | 58 +++++++++++++++++++++ vite.config.ts | 12 +++++ 39 files changed, 523 insertions(+), 336 deletions(-) create mode 100644 src/components/LanguageSelect.tsx create mode 100644 src/utils/textWrap.test.ts create mode 100644 src/utils/textWrap.ts diff --git a/src/App.css b/src/App.css index 433fb2b..973af65 100644 --- a/src/App.css +++ b/src/App.css @@ -2601,3 +2601,15 @@ a.statusbar-legal-link { min-width: 0; flex: 1; } + +/* Longer translated labels (e.g. Portuguese "Opacidade", Finnish "Peittävyys") must not overflow the palette */ +.tool-palette { + min-width: 86px; +} + +.tool-palette .tool-label { + white-space: normal; + overflow-wrap: anywhere; + text-align: center; + line-height: 1.2; +} diff --git a/src/components/AnnotationsPanel.tsx b/src/components/AnnotationsPanel.tsx index ed60b37..69184d7 100644 --- a/src/components/AnnotationsPanel.tsx +++ b/src/components/AnnotationsPanel.tsx @@ -11,6 +11,7 @@ import Tooltip from './Tooltip' import BottomSheet from './BottomSheet' import SlideDrawer from './SlideDrawer' import AnnotationAdvertisement from './AnnotationAdvertisement' +import { useT } from '../i18n' function AnnotationItem({ annotation, @@ -23,6 +24,7 @@ function AnnotationItem({ selected: boolean layoutMode: LayoutMode }) { + const t = useT() const setSelectedId = useAppStore((s) => s.setSelectedId) const updateAnnotation = useAppStore((s) => s.updateAnnotation) const removeAnnotation = useAppStore((s) => s.removeAnnotation) @@ -60,7 +62,7 @@ function AnnotationItem({