+
+
diff --git a/src/lib/Icon.svelte b/src/lib/components/Icon.svelte
similarity index 55%
rename from src/lib/Icon.svelte
rename to src/lib/components/Icon.svelte
index eb1410a..a62553b 100644
--- a/src/lib/Icon.svelte
+++ b/src/lib/components/Icon.svelte
@@ -1,5 +1,6 @@
diff --git a/src/lib/Photo.svelte b/src/lib/components/Photo.svelte
similarity index 100%
rename from src/lib/Photo.svelte
rename to src/lib/components/Photo.svelte
diff --git a/src/lib/data/album.js b/src/lib/data/album.js
new file mode 100644
index 0000000..3aefb4c
--- /dev/null
+++ b/src/lib/data/album.js
@@ -0,0 +1,5 @@
+import { writable } from "svelte/store";
+
+const album = {error: "Not found"};
+/** @type {import('svelte/store').Writable} */
+export default writable(album);
diff --git a/src/lib/data/language.js b/src/lib/data/language.js
new file mode 100644
index 0000000..ed42688
--- /dev/null
+++ b/src/lib/data/language.js
@@ -0,0 +1,69 @@
+import { writable, derived } from "svelte/store";
+
+/** @type {import('svelte/store').Writable} */
+export const language = writable('de');
+
+/**
+ * @type {Record>}
+ */
+const translations = {
+ de: {
+ 'gallery': 'Galerie',
+ 'album': 'Album',
+ 'albums': 'Alben',
+ 'photo': 'Foto',
+ 'photos': 'Fotos',
+ 'video': 'Video',
+ 'videos': 'Videos',
+ 'back': 'Zurück',
+ 'small': 'Klein',
+ 'medium': 'Mittel',
+ 'large': 'Groß',
+ 'open': 'Öffnen',
+ 'download': 'Herunterladen',
+ 'download-all': 'Alle herunterladen',
+ },
+ en: {
+ 'gallery': 'Gallery',
+ 'album': 'Album',
+ 'albums': 'Albums',
+ 'photo': 'Photo',
+ 'photos': 'Photos',
+ 'video': 'Video',
+ 'videos': 'Videos',
+ 'back': 'Back',
+ 'small': 'Small',
+ 'medium': 'Medium',
+ 'large': 'Large',
+ 'open': 'Open',
+ 'download': 'Download',
+ 'download-all': 'Download all',
+ }
+};
+
+export const str = derived(language, $language => {
+ /**
+ * @param {string} key
+ * @param {...any} args
+ */
+ function translate(key, ...args) {
+ const str = translations[$language][key];
+ if (str === undefined) return key;
+ return str.replace(/\{(\d+)\}/g, (_, i) => args[i]);
+ }
+ return translate;
+});
+
+export const strf = derived(language, $language => {
+ /**
+ * @param {Translation | string} translations
+ * @param {...any} args
+ */
+ function translate(translations, ...args) {
+ if (typeof translations === 'string') return translations;
+ const str = translations[$language];
+ if (str === undefined) return translations.de;
+ return str.replace(/\{(\w+)\}/g, (_, i) => args[i]);
+ }
+ return translate;
+});
diff --git a/src/lib/data/licenses.json b/src/lib/data/licenses.json
new file mode 100644
index 0000000..97e2a60
--- /dev/null
+++ b/src/lib/data/licenses.json
@@ -0,0 +1,73 @@
+[
+ {
+ "type": "cc0",
+ "title": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
+ "text": {
+ "de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, ohne um weitere Erlaubnis bitten zu müssen.",
+ "en": "You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission."
+ },
+ "url": "https://creativecommons.org/publicdomain/zero/1.0/"
+ },
+ {
+ "type": "cc-by",
+ "title": "Creative Commons Attribution 4.0 International (CC BY 4.0)",
+ "text": {
+ "de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, wenn Sie den Namen des Autors nennen, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden.",
+ "en": "You can copy, modify, distribute and perform the work, even for commercial purposes, if you give appropriate credit, mention the license and indicate if changes were made."
+ },
+ "url": "https://creativecommons.org/licenses/by/4.0/"
+ },
+ {
+ "type": "cc-by-sa",
+ "title": "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)",
+ "text": {
+ "de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, wenn Sie den Namen des Autors nennen, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden. Wenn Sie das Werk verändern, dürfen Sie das veränderte Werk nur unter derselben Lizenz verbreiten.",
+ "en": "You can copy, modify, distribute and perform the work, even for commercial purposes, if you give appropriate credit, mention the license and indicate if changes were made. If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original."
+ },
+ "url": "https://creativecommons.org/licenses/by-sa/4.0/"
+ },
+ {
+ "type": "cc-by-nc",
+ "title": "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)",
+ "text": {
+ "de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, wenn Sie den Namen des Autors nennen, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden. Sie dürfen das Werk nicht für kommerzielle Zwecke nutzen.",
+ "en": "You can copy, modify, distribute and perform the work, if you give appropriate credit, mention the license and indicate if changes were made. You may use the material only for non commercial purposes."
+ },
+ "url": "https://creativecommons.org/licenses/by-nc/4.0/"
+ },
+ {
+ "type": "cc-by-nc-sa",
+ "title": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)",
+ "text": {
+ "de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, wenn Sie den Namen des Autors nennen, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden. Sie dürfen das Werk nicht für kommerzielle Zwecke nutzen. Wenn Sie das Werk verändern, dürfen Sie das veränderte Werk nur unter derselben Lizenz verbreiten.",
+ "en": "You can copy, modify, distribute and perform the work, if you give appropriate credit, mention the license and indicate if changes were made. You may use the material only for non commercial purposes. If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original."
+ },
+ "url": "https://creativecommons.org/licenses/by-nc-sa/4.0/"
+ },
+ {
+ "type": "cc-by-nd",
+ "title": "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)",
+ "text": {
+ "de": "Sie sind berechtigt, dieses Werk zu kopieren, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, wenn Sie den Namen des Autors nennen und die Lizenz erwähnen. Sie dürfen das Werk nicht verändern.",
+ "en": "You can copy, distribute and perform the work, even for commercial purposes, if you give appropriate credit and mention the license. You may not alter the work in any way."
+ },
+ "url": "https://creativecommons.org/licenses/by-nd/4.0/"
+ },
+ {
+ "type": "cc-by-nc-nd",
+ "title": "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)",
+ "text": {
+ "de": "Sie sind berechtigt, dieses Werk zu kopieren, verbreiten und aufzuführen, wenn Sie den Namen des Autors nennen und die Lizenz erwähnen. Sie dürfen das Werk nicht verändern und nicht für kommerzielle Zwecke nutzen.",
+ "en": "You can copy, distribute and perform the work, if you give appropriate credit and mention the license. You may not alter the work in any way and may use the material only for non commercial purposes."
+ },
+ "url": "https://creativecommons.org/licenses/by-nc-nd/4.0/"
+ },
+ {
+ "type": "all-rights-reserved",
+ "title": "All rights reserved",
+ "text": {
+ "de": "Alle Rechte vorbehalten. Dieses Werk darf ohne Erlaubnis des Autors nicht kopiert, verändert, verbreitet oder aufgeführt werden.",
+ "en": "All rights reserved. This work may not be copied, modified, distributed or performed without the permission of the author."
+ }
+ }
+]
diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts
new file mode 100644
index 0000000..d3fc40b
--- /dev/null
+++ b/src/lib/types.d.ts
@@ -0,0 +1,43 @@
+type TranslationKey = 'de' | 'en';
+
+type Translation = Record;
+
+type LicenseType = 'cc0' | 'cc-by' | 'cc-by-sa' | 'cc-by-nc' | 'cc-by-nc-sa' | 'cc-by-nd' | 'cc-by-nc-nd' | 'all-rights-reserved';
+
+type License = {
+ type: LicenseType;
+ title: Translation | string;
+ text: Translation | string;
+ url?: string;
+}
+
+type Metadata = {
+ title: Translation | string;
+ description?: Translation | string;
+ authors?: string[] | string;
+ place?: string;
+ tags?: string[];
+ license?: License;
+}
+
+type AlbumMetadata = Metadata & {
+ date: string; // ISO 8601, e.g. 2020-12-24, used for sorting
+ cover?: string;
+};
+
+type ItemMetadata = Metadata & {
+ item: string;
+ timestamp?: string; // ISO 8601, e.g. 2020-12-24T12:00:00Z
+};
+
+type Item = ItemMetadata;
+
+type Album = AlbumMetadata & {
+ slug: string;
+ uriTimestamp?: string;
+ items: Item[];
+};
+
+type ApiError = {
+ error: string;
+};
diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte
index 10ca54c..3e1e823 100644
--- a/src/routes/+error.svelte
+++ b/src/routes/+error.svelte
@@ -2,5 +2,30 @@
import { page } from '$app/stores';
-