@gramps-connect/story 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/editor.js +691 -99
- package/dist/editor.js.map +4 -4
- package/dist/types/editor/StoryEditor.d.ts +1 -1
- package/dist/types/editor/index.d.ts +2 -2
- package/dist/types/editor/mapEditorStrings.d.ts +3 -0
- package/dist/types/editor/strings.d.ts +17 -0
- package/dist/types/editor/translations/de.d.ts +4 -0
- package/dist/types/editor/translations/es.d.ts +4 -0
- package/dist/types/editor/translations/fr.d.ts +4 -0
- package/dist/types/editor/translations/index.d.ts +7 -0
- package/dist/types/editor/translations/nl.d.ts +4 -0
- package/package.json +1 -1
- package/src/editor/MapEditor.tsx +23 -10
- package/src/editor/PlaceMapDialog.tsx +3 -7
- package/src/editor/StoryEditor.tsx +10 -4
- package/src/editor/index.ts +2 -2
- package/src/editor/mapEditorStrings.ts +10 -2
- package/src/editor/strings.ts +43 -2
- package/src/editor/translations/de.ts +140 -0
- package/src/editor/translations/es.ts +140 -0
- package/src/editor/translations/fr.ts +140 -0
- package/src/editor/translations/index.ts +15 -0
- package/src/editor/translations/nl.ts +140 -0
|
@@ -37,4 +37,4 @@ export interface StoryEditorProps {
|
|
|
37
37
|
}
|
|
38
38
|
/** The default prepareImage. */
|
|
39
39
|
export declare function prepareImage(file: Blob): Promise<NewImage>;
|
|
40
|
-
export declare function StoryEditor({ initialStory, initialFiles, onChange, prepareImage: prepare, toolbar, brand, dark, strings: stringOverrides, playerStrings, parseDateText, placeSearch, mapStrings, }: StoryEditorProps): import("react").JSX.Element;
|
|
40
|
+
export declare function StoryEditor({ initialStory, initialFiles, onChange, prepareImage: prepare, toolbar, brand, dark, strings: stringOverrides, playerStrings, parseDateText, placeSearch, mapStrings: mapStringOverrides, }: StoryEditorProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { prepareImage, StoryEditor, type StoryEditorProps } from "./StoryEditor";
|
|
2
2
|
export { MapEditor, MapEditorHelp, type MapEditorHandle, type MapEditorOverlay, type MapEditorProps, type MapEditorResult, type MapEditorStatus, } from "./MapEditor";
|
|
3
|
-
export { MAP_EDITOR_ENGLISH, type MapEditorStrings } from "./mapEditorStrings";
|
|
3
|
+
export { MAP_EDITOR_ENGLISH, mapEditorStringsFor, type MapEditorStrings } from "./mapEditorStrings";
|
|
4
4
|
export { PhotosField, type PhotosFieldProps } from "./PhotosField";
|
|
5
5
|
export { PlaceField, type PlaceFieldProps, type PlaceUpdate } from "./PlaceField";
|
|
6
6
|
export { PlaceMapDialog, type PlaceMapDialogProps } from "./PlaceMapDialog";
|
|
@@ -10,5 +10,5 @@ export { StoryTextField, type StoryTextFieldProps } from "./StoryTextField";
|
|
|
10
10
|
export { SlideList, type SlideListProps } from "./SlideList";
|
|
11
11
|
export { DateField, type DateFieldProps } from "./DateField";
|
|
12
12
|
export { suggestEdtf, type DateTextParser } from "./dates";
|
|
13
|
-
export { EDITOR_ENGLISH, type EditorStrings } from "./strings";
|
|
13
|
+
export { browserLanguages, EDITOR_ENGLISH, EDITOR_LANGUAGES, editorLanguage, editorStringsFor, type EditorStrings } from "./strings";
|
|
14
14
|
export { addImage, addSlide, blankStory, COALESCE_MS, deleteSlide, duplicateSlide, findMedia, formatLatLon, moveSlide, nextSlideId, parseLatLon, patchPlace, pruneMedia, record, redo, roundDegrees, setSlidePhotos, slideDate, startHistory, storyFileName, storyPlaceChoices, undo, updateMedia, updateSlide, updateStory, usedMedia, type History, type NewImage, } from "./model";
|
|
@@ -48,3 +48,6 @@ export interface MapEditorStrings {
|
|
|
48
48
|
helpImageRemove: string;
|
|
49
49
|
}
|
|
50
50
|
export declare const MAP_EDITOR_ENGLISH: MapEditorStrings;
|
|
51
|
+
/** The map editor's words in the first of `languages` it has; English
|
|
52
|
+
* when none. */
|
|
53
|
+
export declare function mapEditorStringsFor(...languages: (string | undefined)[]): MapEditorStrings;
|
|
@@ -5,6 +5,7 @@ export interface EditorStrings {
|
|
|
5
5
|
language: string;
|
|
6
6
|
languageHint: string;
|
|
7
7
|
map: string;
|
|
8
|
+
mapHint: string;
|
|
8
9
|
mapStandard: string;
|
|
9
10
|
mapHistorical: string;
|
|
10
11
|
mapAuto: string;
|
|
@@ -46,6 +47,8 @@ export interface EditorStrings {
|
|
|
46
47
|
placeKeeps: (what: string) => string;
|
|
47
48
|
editMap: string;
|
|
48
49
|
editMapHint: string;
|
|
50
|
+
/** The map editor's help on adding an image, where images are files. */
|
|
51
|
+
mapHelpImage: string;
|
|
49
52
|
mapDialogTitle: (placeName: string | undefined) => string;
|
|
50
53
|
mapHelp: string;
|
|
51
54
|
importShapes: string;
|
|
@@ -78,3 +81,17 @@ export interface EditorStrings {
|
|
|
78
81
|
openingSlide: string;
|
|
79
82
|
}
|
|
80
83
|
export declare const EDITOR_ENGLISH: EditorStrings;
|
|
84
|
+
/** The browser's languages, most preferred first (none outside one). */
|
|
85
|
+
export declare function browserLanguages(): readonly string[];
|
|
86
|
+
/** The first of `languages` (BCP 47 tags, or Gramps' de_AT form) the
|
|
87
|
+
* editors have words for, as its base code: "de-CH" is "de". English
|
|
88
|
+
* counts, so "en-US, de" stays English. */
|
|
89
|
+
export declare function editorLanguage(languages: readonly (string | undefined)[]): string | undefined;
|
|
90
|
+
/** The translations for the first of `languages` the editors have;
|
|
91
|
+
* none for English. */
|
|
92
|
+
export declare function translationFor(languages: readonly (string | undefined)[]): import("./translations").EditorTranslation | undefined;
|
|
93
|
+
/** The editor's words in the first of `languages` it has; English when
|
|
94
|
+
* none. */
|
|
95
|
+
export declare function editorStringsFor(...languages: (string | undefined)[]): EditorStrings;
|
|
96
|
+
/** Languages the editors have words for, besides English. */
|
|
97
|
+
export declare const EDITOR_LANGUAGES: string[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EditorStrings } from "../strings";
|
|
2
|
+
import type { MapEditorStrings } from "../mapEditorStrings";
|
|
3
|
+
export interface EditorTranslation {
|
|
4
|
+
editor: EditorStrings;
|
|
5
|
+
mapEditor: MapEditorStrings;
|
|
6
|
+
}
|
|
7
|
+
export declare const TRANSLATIONS: Record<string, EditorTranslation>;
|
package/package.json
CHANGED
package/src/editor/MapEditor.tsx
CHANGED
|
@@ -35,7 +35,8 @@ import {
|
|
|
35
35
|
} from "../mapEdit";
|
|
36
36
|
import { mapStyleUrl } from "../player/mapStyles";
|
|
37
37
|
import { defaultColors } from "../player/theme";
|
|
38
|
-
import {
|
|
38
|
+
import { mapEditorStringsFor, type MapEditorStrings } from "./mapEditorStrings";
|
|
39
|
+
import { browserLanguages } from "./strings";
|
|
39
40
|
|
|
40
41
|
type DrawMode = "point" | "linestring" | "polygon" | "rectangle" | "select";
|
|
41
42
|
|
|
@@ -54,6 +55,9 @@ const COLOR_SWATCHES = [
|
|
|
54
55
|
"#2a78d6", "#d64545", "#3aa657", "#e0a325", "#8654c9", "#2aa7a0", "#e0678a", "#555555",
|
|
55
56
|
];
|
|
56
57
|
|
|
58
|
+
/** A region's fill with no opacity of its own, as the maps and the story
|
|
59
|
+
* player draw it; also a new region's starting opacity. */
|
|
60
|
+
const UNSET_OPACITY = 0.25;
|
|
57
61
|
const EMPTY_LABEL_PREVIEW: FeatureCollection = { type: "FeatureCollection", features: [] };
|
|
58
62
|
|
|
59
63
|
/** An image overlay as the host gives and gets it: `image` is the host's
|
|
@@ -313,7 +317,7 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
|
|
|
313
317
|
initial, initialView, resolveImage, releaseImage, onAddImage, onStatus, onError, dark = false, defaultColor, labelColors,
|
|
314
318
|
toolbarTop = 12, overlayNames = true, renderOverlayOptions, strings: stringOverrides,
|
|
315
319
|
}, ref) {
|
|
316
|
-
const strings = { ...
|
|
320
|
+
const strings = { ...mapEditorStringsFor(...browserLanguages()), ...stringOverrides };
|
|
317
321
|
// Callbacks and words the once-per-map listeners below read, kept live.
|
|
318
322
|
const live = useRef({ strings, resolveImage, releaseImage, onError });
|
|
319
323
|
live.current = { strings, resolveImage, releaseImage, onError };
|
|
@@ -356,9 +360,14 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
|
|
|
356
360
|
// both a newly-finished region's own opacity and, while one is selected,
|
|
357
361
|
// what changing this edits in place. 0.25 matches Gramps Connect's map
|
|
358
362
|
// view default, so an untouched region renders the same there.
|
|
359
|
-
const [opacity, setOpacity] = useState(
|
|
363
|
+
const [opacity, setOpacity] = useState(UNSET_OPACITY);
|
|
360
364
|
const opacityRef = useRef(opacity);
|
|
361
365
|
opacityRef.current = opacity;
|
|
366
|
+
// How a loaded shape with no color of its own is drawn: as the maps draw
|
|
367
|
+
// it (their mark color), not in whatever the toolbar's color is now --
|
|
368
|
+
// and it's saved still without one.
|
|
369
|
+
const unsetColorRef = useRef(defaultColor ?? defaultColors(dark).mark);
|
|
370
|
+
unsetColorRef.current = defaultColor ?? defaultColors(dark).mark;
|
|
362
371
|
// The selected point/region's own `name` (a point's label text, or a
|
|
363
372
|
// region's purely-descriptive name), mirroring `color`'s own pattern --
|
|
364
373
|
// NOT derived from getSnapshot() on every render, since
|
|
@@ -483,12 +492,12 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
|
|
|
483
492
|
// and terra-draw calls a styling function fresh on every render, not
|
|
484
493
|
// just at construction time.
|
|
485
494
|
const readColor = (feature: GeoJSONStoreFeatures): HexColor =>
|
|
486
|
-
(feature.properties.color as HexColor | undefined) ?? (
|
|
495
|
+
(feature.properties.color as HexColor | undefined) ?? (unsetColorRef.current as HexColor);
|
|
487
496
|
// Same pattern as readColor, for a region's own `opacity` property --
|
|
488
497
|
// only polygon/rectangle modes take a `fillOpacity` style at all (point/
|
|
489
498
|
// line have nothing to fill).
|
|
490
499
|
const readOpacity = (feature: GeoJSONStoreFeatures): number =>
|
|
491
|
-
(feature.properties.opacity as number | undefined) ??
|
|
500
|
+
(feature.properties.opacity as number | undefined) ?? UNSET_OPACITY;
|
|
492
501
|
|
|
493
502
|
const draw = new TerraDraw({
|
|
494
503
|
adapter: new TerraDrawMapLibreGLAdapter({ map }),
|
|
@@ -585,8 +594,9 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
|
|
|
585
594
|
const feature = id ? draw.getSnapshot().find((f) => String(f.id) === id) : undefined;
|
|
586
595
|
setLabelText((feature?.properties?.name as string | undefined) ?? "");
|
|
587
596
|
if (feature) {
|
|
588
|
-
|
|
589
|
-
|
|
597
|
+
// An uncolored shape shows the color it's drawn in.
|
|
598
|
+
setColor((feature.properties?.color as string | undefined) ?? unsetColorRef.current);
|
|
599
|
+
setOpacity((feature.properties?.opacity as number | undefined) ?? UNSET_OPACITY);
|
|
590
600
|
// Mirrors the overlay mousedown handler's own deselectFeature call
|
|
591
601
|
// -- an image overlay and a drawn shape are mutually exclusive
|
|
592
602
|
// selections, so a shape becoming selected clears any overlay
|
|
@@ -1227,14 +1237,17 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
|
|
|
1227
1237
|
const name = (f.properties?.name as string | undefined)?.trim();
|
|
1228
1238
|
// Opacity only for a region (Polygon) -- meaningless on a point/
|
|
1229
1239
|
// line, which have nothing to fill (see readOpacity/opacityRef).
|
|
1240
|
+
// Color and opacity only as the shape has them: a drawn shape got
|
|
1241
|
+
// both when finished, and one loaded without them stays that way.
|
|
1230
1242
|
const regionOpacity = f.geometry.type === "Polygon"
|
|
1231
|
-
?
|
|
1243
|
+
? f.properties?.opacity as number | undefined
|
|
1232
1244
|
: undefined;
|
|
1245
|
+
const color = f.properties?.color as string | undefined;
|
|
1233
1246
|
const sourceKey = f.properties?.sourceKey;
|
|
1234
1247
|
return {
|
|
1235
1248
|
...(typeof sourceKey === "string" ? { sourceKey } : {}),
|
|
1236
1249
|
geometry: f.geometry as EditableGeometry,
|
|
1237
|
-
|
|
1250
|
+
...(color !== undefined ? { color } : {}),
|
|
1238
1251
|
...(name ? { name } : {}),
|
|
1239
1252
|
...(regionOpacity !== undefined ? { opacity: regionOpacity } : {}),
|
|
1240
1253
|
};
|
|
@@ -1498,7 +1511,7 @@ export const MapEditor = forwardRef<MapEditorHandle, MapEditorProps>(function Ma
|
|
|
1498
1511
|
/** How to use the map editor, for the host to show where it likes (Gramps
|
|
1499
1512
|
* Connect: an info button's modal). */
|
|
1500
1513
|
export function MapEditorHelp({ strings: stringOverrides, images = true }: { strings?: Partial<MapEditorStrings>; images?: boolean }) {
|
|
1501
|
-
const strings = { ...
|
|
1514
|
+
const strings = { ...mapEditorStringsFor(...browserLanguages()), ...stringOverrides };
|
|
1502
1515
|
return (
|
|
1503
1516
|
<Stack gap="md">
|
|
1504
1517
|
<Text size="sm" fw={600}>{strings.helpDrawing}</Text>
|
|
@@ -18,12 +18,6 @@ import type { EditorStrings } from "./strings";
|
|
|
18
18
|
|
|
19
19
|
const BAR_HEIGHT = 56;
|
|
20
20
|
|
|
21
|
-
/** Where the map editor's English is Gramps Connect's (images come from
|
|
22
|
-
* its media there), the story editor's own. */
|
|
23
|
-
const STORY_MAP_WORDS: Partial<MapEditorStrings> = {
|
|
24
|
-
helpImage: "Image: add a picture file, such as a scan of an old map -- see",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
21
|
export interface PlaceMapDialogProps {
|
|
28
22
|
place: Place | undefined;
|
|
29
23
|
doc: StoryDoc;
|
|
@@ -83,7 +77,9 @@ function OverlayDateField({ value, language, parse, strings, onChange }: {
|
|
|
83
77
|
export function PlaceMapDialog({
|
|
84
78
|
place, doc, files, dark, parseDateText, strings, mapStrings: mapStringOverrides, onCancel, onDone,
|
|
85
79
|
}: PlaceMapDialogProps) {
|
|
86
|
-
|
|
80
|
+
// The map editor's help on images is Gramps Connect's (images come from
|
|
81
|
+
// its media there); here they're files.
|
|
82
|
+
const mapStrings = { helpImage: strings.mapHelpImage, ...mapStringOverrides };
|
|
87
83
|
const editorRef = useRef<MapEditorHandle>(null);
|
|
88
84
|
const initial = useMemo(() => placeToEditor(place), [place]);
|
|
89
85
|
// The story's media and files with what's been added here.
|
|
@@ -30,7 +30,8 @@ import type { MapEditorStrings } from "./mapEditorStrings";
|
|
|
30
30
|
import { WIKIDATA_PLACE_SEARCH, type PlaceSearch } from "./placeSearch";
|
|
31
31
|
import { SlideList } from "./SlideList";
|
|
32
32
|
import { StoryTextField } from "./StoryTextField";
|
|
33
|
-
import {
|
|
33
|
+
import { browserLanguages, editorStringsFor, type EditorStrings } from "./strings";
|
|
34
|
+
import { mapEditorStringsFor } from "./mapEditorStrings";
|
|
34
35
|
|
|
35
36
|
const BAR_HEIGHT = 56;
|
|
36
37
|
/** The story's own settings, in place of a slide index. */
|
|
@@ -102,9 +103,14 @@ function Thumb({ doc, slide, resolveMedia }: { doc: StoryDoc; slide: Slide; reso
|
|
|
102
103
|
|
|
103
104
|
export function StoryEditor({
|
|
104
105
|
initialStory, initialFiles, onChange, prepareImage: prepare = prepareImage, toolbar, brand, dark = false,
|
|
105
|
-
strings: stringOverrides, playerStrings, parseDateText = suggestEdtf, placeSearch = WIKIDATA_PLACE_SEARCH, mapStrings,
|
|
106
|
+
strings: stringOverrides, playerStrings, parseDateText = suggestEdtf, placeSearch = WIKIDATA_PLACE_SEARCH, mapStrings: mapStringOverrides,
|
|
106
107
|
}: StoryEditorProps) {
|
|
107
|
-
|
|
108
|
+
// In the browser's language where there's a translation, over English.
|
|
109
|
+
const strings = useMemo(() => ({ ...editorStringsFor(...browserLanguages()), ...stringOverrides }), [stringOverrides]);
|
|
110
|
+
const mapStrings = useMemo(
|
|
111
|
+
() => ({ ...mapEditorStringsFor(...browserLanguages()), helpImage: strings.mapHelpImage, ...mapStringOverrides }),
|
|
112
|
+
[strings, mapStringOverrides],
|
|
113
|
+
);
|
|
108
114
|
const [history, setHistory] = useState<History>(() => startHistory(initialStory));
|
|
109
115
|
const [selected, setSelected] = useState(0);
|
|
110
116
|
const [presenting, setPresenting] = useState(false);
|
|
@@ -271,7 +277,7 @@ export function StoryEditor({
|
|
|
271
277
|
onChange={(e) => apply(updateStory(doc, { language: e.currentTarget.value.trim() || undefined }), "story:language")}
|
|
272
278
|
/>
|
|
273
279
|
<Select
|
|
274
|
-
label={strings.map} w={320} allowDeselect={false}
|
|
280
|
+
label={strings.map} description={strings.mapHint} w={320} allowDeselect={false}
|
|
275
281
|
data={[
|
|
276
282
|
{ value: "auto", label: strings.mapAuto },
|
|
277
283
|
{ value: "standard", label: strings.mapStandard },
|
package/src/editor/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { prepareImage, StoryEditor, type StoryEditorProps } from "./StoryEditor"
|
|
|
2
2
|
export {
|
|
3
3
|
MapEditor, MapEditorHelp, type MapEditorHandle, type MapEditorOverlay, type MapEditorProps, type MapEditorResult, type MapEditorStatus,
|
|
4
4
|
} from "./MapEditor";
|
|
5
|
-
export { MAP_EDITOR_ENGLISH, type MapEditorStrings } from "./mapEditorStrings";
|
|
5
|
+
export { MAP_EDITOR_ENGLISH, mapEditorStringsFor, type MapEditorStrings } from "./mapEditorStrings";
|
|
6
6
|
export { PhotosField, type PhotosFieldProps } from "./PhotosField";
|
|
7
7
|
export { PlaceField, type PlaceFieldProps, type PlaceUpdate } from "./PlaceField";
|
|
8
8
|
export { PlaceMapDialog, type PlaceMapDialogProps } from "./PlaceMapDialog";
|
|
@@ -12,7 +12,7 @@ export { StoryTextField, type StoryTextFieldProps } from "./StoryTextField";
|
|
|
12
12
|
export { SlideList, type SlideListProps } from "./SlideList";
|
|
13
13
|
export { DateField, type DateFieldProps } from "./DateField";
|
|
14
14
|
export { suggestEdtf, type DateTextParser } from "./dates";
|
|
15
|
-
export { EDITOR_ENGLISH, type EditorStrings } from "./strings";
|
|
15
|
+
export { browserLanguages, EDITOR_ENGLISH, EDITOR_LANGUAGES, editorLanguage, editorStringsFor, type EditorStrings } from "./strings";
|
|
16
16
|
export {
|
|
17
17
|
addImage, addSlide, blankStory, COALESCE_MS, deleteSlide, duplicateSlide, findMedia, formatLatLon, moveSlide, nextSlideId, parseLatLon,
|
|
18
18
|
patchPlace, pruneMedia, record, redo, roundDegrees, setSlidePhotos, slideDate, startHistory, storyFileName, storyPlaceChoices, undo,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// The map editor's words
|
|
2
|
-
//
|
|
1
|
+
// The map editor's words, in English and the story editor's other
|
|
2
|
+
// languages (translations/). Gramps Connect passes its own where it has
|
|
3
|
+
// them (its t() is keyed by these same English texts).
|
|
4
|
+
import { translationFor } from "./strings";
|
|
3
5
|
|
|
4
6
|
export interface MapEditorStrings {
|
|
5
7
|
point: string;
|
|
@@ -107,3 +109,9 @@ export const MAP_EDITOR_ENGLISH: MapEditorStrings = {
|
|
|
107
109
|
+ "working on the image's exact current shape either way.",
|
|
108
110
|
helpImageRemove: "Click its × handle to remove it.",
|
|
109
111
|
};
|
|
112
|
+
|
|
113
|
+
/** The map editor's words in the first of `languages` it has; English
|
|
114
|
+
* when none. */
|
|
115
|
+
export function mapEditorStringsFor(...languages: (string | undefined)[]): MapEditorStrings {
|
|
116
|
+
return translationFor(languages)?.mapEditor ?? MAP_EDITOR_ENGLISH;
|
|
117
|
+
}
|
package/src/editor/strings.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
// The editor's own words
|
|
2
|
-
// (
|
|
1
|
+
// The editor's own words, in English and the player's other languages
|
|
2
|
+
// (translations/). The editor picks by the browser's languages; a host
|
|
3
|
+
// with its own translations (Gramps Connect) passes StoryEditor's
|
|
4
|
+
// `strings` instead.
|
|
5
|
+
import { TRANSLATIONS } from "./translations";
|
|
3
6
|
|
|
4
7
|
export interface EditorStrings {
|
|
5
8
|
story: string;
|
|
@@ -8,6 +11,7 @@ export interface EditorStrings {
|
|
|
8
11
|
language: string;
|
|
9
12
|
languageHint: string;
|
|
10
13
|
map: string;
|
|
14
|
+
mapHint: string;
|
|
11
15
|
mapStandard: string;
|
|
12
16
|
mapHistorical: string;
|
|
13
17
|
mapAuto: string;
|
|
@@ -49,6 +53,8 @@ export interface EditorStrings {
|
|
|
49
53
|
placeKeeps: (what: string) => string;
|
|
50
54
|
editMap: string;
|
|
51
55
|
editMapHint: string;
|
|
56
|
+
/** The map editor's help on adding an image, where images are files. */
|
|
57
|
+
mapHelpImage: string;
|
|
52
58
|
mapDialogTitle: (placeName: string | undefined) => string;
|
|
53
59
|
mapHelp: string;
|
|
54
60
|
importShapes: string;
|
|
@@ -88,6 +94,7 @@ export const EDITOR_ENGLISH: EditorStrings = {
|
|
|
88
94
|
language: "Language",
|
|
89
95
|
languageHint: "The language the story is written in, e.g. en, de or fr-CA. Dates are read in it.",
|
|
90
96
|
map: "Map",
|
|
97
|
+
mapHint: "How the map starts when the story plays. Readers can still change it.",
|
|
91
98
|
mapStandard: "Today's map",
|
|
92
99
|
mapHistorical: "Historical maps for each slide's year",
|
|
93
100
|
mapAuto: "Viewer's choice",
|
|
@@ -129,6 +136,7 @@ export const EDITOR_ENGLISH: EditorStrings = {
|
|
|
129
136
|
placeKeeps: (what) => `This place also has ${what}.`,
|
|
130
137
|
editMap: "Edit map",
|
|
131
138
|
editMapHint: "Shapes under the pin (a parish, a route) and scans of old maps laid over it.",
|
|
139
|
+
mapHelpImage: "Image: add a picture file, such as a scan of an old map -- see",
|
|
132
140
|
mapDialogTitle: (placeName) => (placeName ? `Map: ${placeName}` : "Map"),
|
|
133
141
|
mapHelp: "How to use this editor",
|
|
134
142
|
importShapes: "Import shapes",
|
|
@@ -160,3 +168,36 @@ export const EDITOR_ENGLISH: EditorStrings = {
|
|
|
160
168
|
problems: (count) => (count === 1 ? "1 problem to fix before saving" : `${count} problems to fix before saving`),
|
|
161
169
|
openingSlide: "Opening slide",
|
|
162
170
|
};
|
|
171
|
+
|
|
172
|
+
/** The browser's languages, most preferred first (none outside one). */
|
|
173
|
+
export function browserLanguages(): readonly string[] {
|
|
174
|
+
if (typeof navigator === "undefined") return [];
|
|
175
|
+
return navigator.languages?.length ? navigator.languages : [navigator.language];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The first of `languages` (BCP 47 tags, or Gramps' de_AT form) the
|
|
179
|
+
* editors have words for, as its base code: "de-CH" is "de". English
|
|
180
|
+
* counts, so "en-US, de" stays English. */
|
|
181
|
+
export function editorLanguage(languages: readonly (string | undefined)[]): string | undefined {
|
|
182
|
+
for (const tag of languages) {
|
|
183
|
+
const base = tag?.toLowerCase().split(/[-_]/)[0];
|
|
184
|
+
if (base === "en" || (base && Object.prototype.hasOwnProperty.call(TRANSLATIONS, base))) return base;
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The translations for the first of `languages` the editors have;
|
|
190
|
+
* none for English. */
|
|
191
|
+
export function translationFor(languages: readonly (string | undefined)[]) {
|
|
192
|
+
const lang = editorLanguage(languages);
|
|
193
|
+
return lang && lang !== "en" ? TRANSLATIONS[lang] : undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The editor's words in the first of `languages` it has; English when
|
|
197
|
+
* none. */
|
|
198
|
+
export function editorStringsFor(...languages: (string | undefined)[]): EditorStrings {
|
|
199
|
+
return translationFor(languages)?.editor ?? EDITOR_ENGLISH;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Languages the editors have words for, besides English. */
|
|
203
|
+
export const EDITOR_LANGUAGES = Object.keys(TRANSLATIONS);
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { EditorStrings } from "../strings";
|
|
2
|
+
import type { MapEditorStrings } from "../mapEditorStrings";
|
|
3
|
+
|
|
4
|
+
export const editor: EditorStrings = {
|
|
5
|
+
story: "Geschichte",
|
|
6
|
+
storySettings: "Titel, Sprache und Karte",
|
|
7
|
+
title: "Titel",
|
|
8
|
+
language: "Sprache",
|
|
9
|
+
languageHint: "Die Sprache, in der die Geschichte geschrieben ist, z. B. en, de oder fr-CA. Daten werden in ihr gelesen.",
|
|
10
|
+
map: "Karte",
|
|
11
|
+
mapHint: "Wie die Karte beim Abspielen der Geschichte beginnt. Leser können sie trotzdem ändern.",
|
|
12
|
+
mapStandard: "Heutige Karte",
|
|
13
|
+
mapHistorical: "Historische Karten zum Jahr jeder Folie",
|
|
14
|
+
mapAuto: "Wahl des Betrachters",
|
|
15
|
+
mapNone: "Keine Karte",
|
|
16
|
+
slides: "Folien",
|
|
17
|
+
untitledSlide: "Folie ohne Titel",
|
|
18
|
+
addSlide: "Folie hinzufügen",
|
|
19
|
+
duplicateSlide: "Duplizieren",
|
|
20
|
+
deleteSlide: "Löschen",
|
|
21
|
+
moveUp: "Nach oben",
|
|
22
|
+
moveDown: "Nach unten",
|
|
23
|
+
slideTitle: "Titel",
|
|
24
|
+
text: "Text",
|
|
25
|
+
bold: "Fett",
|
|
26
|
+
italic: "Kursiv",
|
|
27
|
+
date: "Datum",
|
|
28
|
+
dateHint: "So, wie Leser es sehen sollen, z. B. um 1850, 3. Mai 1850, zwischen 1850 und 1860.",
|
|
29
|
+
preciseDate: "Genaues Datum (EDTF)",
|
|
30
|
+
preciseDateHint: "Setzt die Folie auf die Zeitleiste und wählt die historischen Karten.",
|
|
31
|
+
preciseDateFollows: "Aus dem geschriebenen Datum ermittelt.",
|
|
32
|
+
preciseDateNotUnderstood: "Kein Datum, das die Zeitleiste verwenden kann.",
|
|
33
|
+
matchDateAsWritten: "Aus dem Datum ermitteln",
|
|
34
|
+
noPreciseDate: "Keines: Diese Folie bleibt außerhalb der Zeitleiste.",
|
|
35
|
+
place: "Ort",
|
|
36
|
+
placeName: "Name",
|
|
37
|
+
removePlace: "Ort entfernen",
|
|
38
|
+
findPlace: "Auf Wikidata suchen",
|
|
39
|
+
findPlaceHint: "Sucht den Namen auf Wikidata, um die Stecknadel dorthin zu setzen. Nur der Name wird gesendet, und nur, wenn Sie es wünschen.",
|
|
40
|
+
findPlaceNone: "Wikidata kennt keinen Ort mit diesem Namen.",
|
|
41
|
+
findPlaceFailed: "Wikidata war nicht erreichbar.",
|
|
42
|
+
findPlaceNoPoint: (name) => `Wikidata hat keine Kartenposition für ${name}.`,
|
|
43
|
+
coordinates: "Breite, Länge",
|
|
44
|
+
coordinatesHint: "z. B. 47.3769, 8.5417",
|
|
45
|
+
coordinatesInvalid: "Zwei Zahlen: Breite (-90 bis 90), dann Länge (-180 bis 180).",
|
|
46
|
+
placeMap: "Karte: klicken, um die Stecknadel zu setzen",
|
|
47
|
+
placeMapHint: "Klicken Sie auf die Karte, um die Stecknadel dorthin zu setzen, oder ziehen Sie die Stecknadel.",
|
|
48
|
+
placeShape: "eine Form",
|
|
49
|
+
placeOverlays: (count) => (count === 1 ? "eine Kartenüberlagerung" : `${count} Kartenüberlagerungen`),
|
|
50
|
+
placeKeeps: (what) => `Dieser Ort hat außerdem ${what}.`,
|
|
51
|
+
editMap: "Karte bearbeiten",
|
|
52
|
+
editMapHint: "Formen unter der Stecknadel (eine Pfarrei, eine Route) und darübergelegte Scans alter Karten.",
|
|
53
|
+
mapHelpImage: "Bild: Fügen Sie eine Bilddatei hinzu, etwa den Scan einer alten Karte – siehe",
|
|
54
|
+
mapDialogTitle: (placeName) => (placeName ? `Karte: ${placeName}` : "Karte"),
|
|
55
|
+
mapHelp: "So benutzen Sie diesen Editor",
|
|
56
|
+
importShapes: "Formen importieren",
|
|
57
|
+
importShapesHint: "Fügt die Punkte, Linien und Flächen einer KML-, GPX- oder GeoJSON-Datei zu dieser Karte hinzu.",
|
|
58
|
+
importFailed: (fileName) => `${fileName} konnte nicht als KML-, GPX- oder GeoJSON-Datei gelesen werden.`,
|
|
59
|
+
importNothing: (fileName) => `${fileName} enthält keine Formen.`,
|
|
60
|
+
overlayAlready: "Dieses Bild ist bereits auf dieser Karte.",
|
|
61
|
+
overlayDate: "Jahre, die sie zeigt",
|
|
62
|
+
overlayDateHint: "z. B. 1850, oder 1850 bis 1880. Mit historischen Karten ist sie auf Folien außerhalb dieser Jahre ausgeblendet.",
|
|
63
|
+
cancel: "Abbrechen",
|
|
64
|
+
done: "Fertig",
|
|
65
|
+
photos: "Fotos",
|
|
66
|
+
photosHint: "Fügen Sie ein Foto hinzu, ziehen Sie es hierher oder fügen Sie es ein. Es wird für den Bildschirm verkleinert, und versteckte Angaben wie Kamera und Standort werden weggelassen.",
|
|
67
|
+
addPhoto: "Foto hinzufügen",
|
|
68
|
+
replacePhoto: "Ersetzen",
|
|
69
|
+
movePhotoUp: "Foto nach oben",
|
|
70
|
+
movePhotoDown: "Foto nach unten",
|
|
71
|
+
removePhoto: "Entfernen",
|
|
72
|
+
caption: "Bildunterschrift",
|
|
73
|
+
credit: "Bildnachweis",
|
|
74
|
+
creditHint: "Wer es aufgenommen hat oder woher es stammt",
|
|
75
|
+
description: "Beschreibung",
|
|
76
|
+
descriptionHint: "Was das Foto zeigt, für alle, die es nicht sehen können.",
|
|
77
|
+
photoFailed: (name) => `${name} konnte nicht verwendet werden: Dieser Browser kann es nicht als Bild lesen.`,
|
|
78
|
+
undo: "Rückgängig",
|
|
79
|
+
redo: "Wiederholen",
|
|
80
|
+
present: "Vorführen",
|
|
81
|
+
preview: "Vorschau",
|
|
82
|
+
problems: (count) => (count === 1 ? "1 Problem vor dem Speichern beheben" : `${count} Probleme vor dem Speichern beheben`),
|
|
83
|
+
openingSlide: "Eröffnungsfolie",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const mapEditor: MapEditorStrings = {
|
|
87
|
+
point: "Punkt",
|
|
88
|
+
line: "Linie",
|
|
89
|
+
polygon: "Polygon",
|
|
90
|
+
rectangle: "Rechteck",
|
|
91
|
+
select: "Auswählen",
|
|
92
|
+
image: "Bild",
|
|
93
|
+
editOptions: "Bearbeitungsoptionen",
|
|
94
|
+
labelText: "Beschriftung",
|
|
95
|
+
regionName: "Name des Gebiets (optional)",
|
|
96
|
+
fillOpacity: "Deckkraft der Füllung",
|
|
97
|
+
scale: "Skalieren",
|
|
98
|
+
transform: "Verzerren",
|
|
99
|
+
overlayName: "Name der Überlagerung (optional)",
|
|
100
|
+
opacity: "Deckkraft",
|
|
101
|
+
stackingOrder: "Stapelreihenfolge",
|
|
102
|
+
removeImage: "Dieses Bild entfernen",
|
|
103
|
+
resizeImage: "Ziehen, um die Größe zu ändern (behält die genaue Form, verzerrt oder nicht)",
|
|
104
|
+
rotateImage: "Ziehen, um zu drehen",
|
|
105
|
+
warpCorner: "Ziehen, um diese Ecke zu verzerren",
|
|
106
|
+
basemapFailed: "Die Grundkarte konnte nicht geladen werden.",
|
|
107
|
+
imageFailed: "Das Bild konnte nicht geladen werden.",
|
|
108
|
+
lockedOne: "1 Form kann hier nicht bearbeitet werden",
|
|
109
|
+
lockedMany: (count) => `${count} Formen können hier nicht bearbeitet werden`,
|
|
110
|
+
lockedExplain: "Zum Beispiel ein Gebiet mit einem Loch oder eines, dessen Umriss sich selbst kreuzt. Es wird gestrichelt gezeichnet und genau so gespeichert, wie es war.",
|
|
111
|
+
helpTitle: "So benutzen Sie diesen Editor",
|
|
112
|
+
helpDrawing: "Zeichnen",
|
|
113
|
+
helpPoint: "Punkt: einmal klicken, um ihn zu setzen.",
|
|
114
|
+
helpLine: "Linie, Polygon: klicken, um jeden Punkt zu setzen, dann erneut auf die Markierung des ersten Punktes klicken, "
|
|
115
|
+
+ "um abzuschließen – sie sieht anders aus, sobald es genug Punkte gibt, um die Form zu schließen. Auch",
|
|
116
|
+
helpEnterFinishes: "schließt sie ab;",
|
|
117
|
+
helpEscCancels: "bricht sie ab.",
|
|
118
|
+
helpRectangle: "Rechteck: von einer Ecke zur gegenüberliegenden Ecke klicken und ziehen.",
|
|
119
|
+
helpImage: "Bild: ein Medienobjekt zum Überlagern wählen – siehe",
|
|
120
|
+
helpImages: "Bilder",
|
|
121
|
+
helpBelow: "unten.",
|
|
122
|
+
helpEditing: "Eine Form bearbeiten",
|
|
123
|
+
helpSelect: "Auswählen: auf eine Form klicken, um sie auszuwählen, dann sie oder einen ihrer Griffe ziehen.",
|
|
124
|
+
helpVertex: "Einen Eckpunkt ziehen, um ihn zu verschieben. Für einen neuen Punkt auf einer Linie, einem Polygon oder Rechteck "
|
|
125
|
+
+ "die kleine Mittelpunktmarkierung zwischen zwei Eckpunkten nach außen ziehen.",
|
|
126
|
+
helpColor: "Das Farbfeld legt die Farbe der nächsten Form fest – oder ändert, wenn eine Form ausgewählt ist, deren Farbe.",
|
|
127
|
+
helpDelete: "Eine ausgewählte Form oder ein Bild löschen mit",
|
|
128
|
+
helpOr: "oder",
|
|
129
|
+
helpRotate: "Ein ausgewähltes Polygon, Rechteck oder eine Linie drehen:",
|
|
130
|
+
helpRotateHandle: "gedrückt halten und dabei einen ihrer Griffe ziehen.",
|
|
131
|
+
helpImageMove: "Irgendwo auf ein Bild klicken und ziehen, um es zu verschieben.",
|
|
132
|
+
helpImageResize: "Seinen ↘-Griff ziehen, um die Größe zu ändern – es wächst oder schrumpft um seine eigene Mitte "
|
|
133
|
+
+ "und behält seine genaue Form (auch bei einem verzerrten Bild).",
|
|
134
|
+
helpImageRotate: "Seinen ↻-Griff ziehen, um es zu drehen – praktisch, um eine gescannte alte Karte auszurichten.",
|
|
135
|
+
helpImageTransform: "Auf „Verzerren“ umschalten, um zusätzlich 4 Eckgriffe zu zeigen, die sich einzeln ziehen lassen "
|
|
136
|
+
+ "(Entzerrung) – für eine gescannte Karte ohne einheitlichen Maßstab oder einheitliche Ausrichtung. "
|
|
137
|
+
+ "Zurück auf „Skalieren“ blendet diese 4 Griffe nur wieder aus; Verschieben, Größe ändern und Drehen "
|
|
138
|
+
+ "wirken in beiden Fällen auf die genaue aktuelle Form des Bildes.",
|
|
139
|
+
helpImageRemove: "Auf seinen ×-Griff klicken, um es zu entfernen.",
|
|
140
|
+
};
|