feat(localization): add i18next extraction helper & refactor backup configuration component to tsx
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { defineConfig } from 'i18next-cli';
|
||||
import { typescriptPlugin } from "./scripts/i18nextExtractionHelper";
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
locales: ['en', 'de', 'nl', 'pt', 'ru', 'fr'],
|
||||
@@ -18,6 +20,7 @@ export default defineConfig({
|
||||
preserveContextVariants: true,
|
||||
|
||||
indentation: 2,
|
||||
sort:false
|
||||
sort: false,
|
||||
},
|
||||
plugins: [typescriptPlugin(["./src/App.tsx"]) ]
|
||||
});
|
||||
Generated
+540
@@ -53,6 +53,7 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^25.6.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.5.3",
|
||||
@@ -64,6 +65,7 @@
|
||||
"globals": "^16.2.0",
|
||||
"i18next-cli": "^1.56.11",
|
||||
"jsdom": "^25.0.1",
|
||||
"memfs": "^4.57.2",
|
||||
"postcss": "^8.5.10",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "~5.8.3",
|
||||
@@ -1618,6 +1620,436 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/buffers": {
|
||||
"version": "17.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz",
|
||||
"integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/codegen": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz",
|
||||
"integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-core": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz",
|
||||
"integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/fs-node-builtins": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-utils": "4.57.2",
|
||||
"thingies": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-fsa": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz",
|
||||
"integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/fs-core": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-builtins": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-utils": "4.57.2",
|
||||
"thingies": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-node": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz",
|
||||
"integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/fs-core": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-builtins": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-utils": "4.57.2",
|
||||
"@jsonjoy.com/fs-print": "4.57.2",
|
||||
"@jsonjoy.com/fs-snapshot": "4.57.2",
|
||||
"glob-to-regex.js": "^1.0.0",
|
||||
"thingies": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-node-builtins": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz",
|
||||
"integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-node-to-fsa": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz",
|
||||
"integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/fs-fsa": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-builtins": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-utils": "4.57.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-node-utils": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz",
|
||||
"integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/fs-node-builtins": "4.57.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-print": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz",
|
||||
"integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/fs-node-utils": "4.57.2",
|
||||
"tree-dump": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-snapshot": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz",
|
||||
"integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/buffers": "^17.65.0",
|
||||
"@jsonjoy.com/fs-node-utils": "4.57.2",
|
||||
"@jsonjoy.com/json-pack": "^17.65.0",
|
||||
"@jsonjoy.com/util": "^17.65.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": {
|
||||
"version": "17.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz",
|
||||
"integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": {
|
||||
"version": "17.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz",
|
||||
"integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": {
|
||||
"version": "17.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz",
|
||||
"integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/base64": "17.67.0",
|
||||
"@jsonjoy.com/buffers": "17.67.0",
|
||||
"@jsonjoy.com/codegen": "17.67.0",
|
||||
"@jsonjoy.com/json-pointer": "17.67.0",
|
||||
"@jsonjoy.com/util": "17.67.0",
|
||||
"hyperdyperid": "^1.2.0",
|
||||
"thingies": "^2.5.0",
|
||||
"tree-dump": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": {
|
||||
"version": "17.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz",
|
||||
"integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/util": "17.67.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": {
|
||||
"version": "17.67.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz",
|
||||
"integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/buffers": "17.67.0",
|
||||
"@jsonjoy.com/codegen": "17.67.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/json-pack": {
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz",
|
||||
"integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/base64": "^1.1.2",
|
||||
"@jsonjoy.com/buffers": "^1.2.0",
|
||||
"@jsonjoy.com/codegen": "^1.0.0",
|
||||
"@jsonjoy.com/json-pointer": "^1.0.2",
|
||||
"@jsonjoy.com/util": "^1.9.0",
|
||||
"hyperdyperid": "^1.2.0",
|
||||
"thingies": "^2.5.0",
|
||||
"tree-dump": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz",
|
||||
"integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/json-pointer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz",
|
||||
"integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/codegen": "^1.0.0",
|
||||
"@jsonjoy.com/util": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/util": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz",
|
||||
"integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/buffers": "^1.0.0",
|
||||
"@jsonjoy.com/codegen": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz",
|
||||
"integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -3038,6 +3470,16 @@
|
||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz",
|
||||
"integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
@@ -5100,6 +5542,23 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-to-regex.js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz",
|
||||
"integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
@@ -5298,6 +5757,16 @@
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hyperdyperid": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
|
||||
"integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.18"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.7.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.3.tgz",
|
||||
@@ -6190,6 +6659,36 @@
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/memfs": {
|
||||
"version": "4.57.2",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz",
|
||||
"integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jsonjoy.com/fs-core": "4.57.2",
|
||||
"@jsonjoy.com/fs-fsa": "4.57.2",
|
||||
"@jsonjoy.com/fs-node": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-builtins": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-to-fsa": "4.57.2",
|
||||
"@jsonjoy.com/fs-node-utils": "4.57.2",
|
||||
"@jsonjoy.com/fs-print": "4.57.2",
|
||||
"@jsonjoy.com/fs-snapshot": "4.57.2",
|
||||
"@jsonjoy.com/json-pack": "^1.11.0",
|
||||
"@jsonjoy.com/util": "^1.9.0",
|
||||
"glob-to-regex.js": "^1.0.1",
|
||||
"thingies": "^2.5.0",
|
||||
"tree-dump": "^1.0.3",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@@ -8003,6 +8502,23 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/thingies": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz",
|
||||
"integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
@@ -8125,6 +8641,23 @@
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tree-dump": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz",
|
||||
"integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/streamich"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||
@@ -8208,6 +8741,13 @@
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.19.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unicorn-magic": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^25.6.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.5.3",
|
||||
@@ -71,6 +72,7 @@
|
||||
"globals": "^16.2.0",
|
||||
"i18next-cli": "^1.56.11",
|
||||
"jsdom": "^25.0.1",
|
||||
"memfs": "^4.57.2",
|
||||
"postcss": "^8.5.10",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "~5.8.3",
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
import type { ExtractedKey, Plugin } from 'i18next-cli'
|
||||
import * as ts from 'typescript'
|
||||
import { vol } from 'memfs'
|
||||
import * as path from 'path'
|
||||
|
||||
// based on https://github.com/i18next/i18next-cli/blob/49188621ae5534d940c7cc86eca1e3ece99506e7/test/plugin.typescript.test.ts
|
||||
|
||||
// --- HELPER FUNCTIONS FOR PLUGIN ---
|
||||
function isTranslationFunction (node: ts.CallExpression): boolean {
|
||||
const expr = node.expression
|
||||
// Matches t('...')
|
||||
if (ts.isIdentifier(expr) && expr.text === 't') return true
|
||||
|
||||
// Matches i18n.t('...')
|
||||
if (ts.isPropertyAccessExpression(expr) && expr.name.text === 't') return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function extractStringsFromType (type: ts.Type): string[] {
|
||||
if (type.isStringLiteral()) {
|
||||
return [type.value]
|
||||
}
|
||||
if (type.isUnion()) {
|
||||
return type.types.flatMap(t => extractStringsFromType(t))
|
||||
}
|
||||
if (type.isIntersection()) {
|
||||
return type.types.flatMap(t => extractStringsFromType(t))
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function typescriptPlugin (
|
||||
entryPoints: string[],
|
||||
options: { defaultNS?: string } = {}
|
||||
): Plugin {
|
||||
const defaultNS = options.defaultNS
|
||||
|
||||
return {
|
||||
name: 'typescript-resolver',
|
||||
async onEnd (keys: Map<string, ExtractedKey>) {
|
||||
// 1. Setup Compiler Options
|
||||
const compilerOptions: ts.CompilerOptions = {
|
||||
allowJs: true,
|
||||
jsx: ts.JsxEmit.ReactJSX,
|
||||
target: ts.ScriptTarget.ESNext,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
||||
esModuleInterop: true
|
||||
}
|
||||
|
||||
// 2. Create a custom CompilerHost that reads from memfs (vol)
|
||||
const host = ts.createCompilerHost(compilerOptions)
|
||||
const originalReadFile = host.readFile
|
||||
const originalFileExists = host.fileExists
|
||||
|
||||
host.readFile = (fileName: string) => {
|
||||
if (vol.existsSync(fileName)) {
|
||||
return vol.readFileSync(fileName, 'utf-8') as string
|
||||
}
|
||||
return originalReadFile(fileName)
|
||||
}
|
||||
|
||||
host.fileExists = (fileName: string) => {
|
||||
if (vol.existsSync(fileName)) return true
|
||||
return originalFileExists(fileName)
|
||||
}
|
||||
|
||||
// Override module resolution to look in memfs
|
||||
host.resolveModuleNameLiterals = (moduleLiterals, containingFile, redirectedReference, options, containingSourceFile, reusedNames) => {
|
||||
return moduleLiterals.map(moduleLiteral => {
|
||||
const moduleName = moduleLiteral.text
|
||||
// Simple resolution for test: resolve relative paths against the containing file's directory
|
||||
if (moduleName.startsWith('.')) {
|
||||
const dir = path.dirname(containingFile)
|
||||
const candidates: [string, ts.Extension][] = [
|
||||
[path.join(dir, moduleName + '.tsx'), ts.Extension.Tsx],
|
||||
[path.join(dir, moduleName + '.ts'), ts.Extension.Ts],
|
||||
]
|
||||
for (const [resolvedPath, ext] of candidates) {
|
||||
if (vol.existsSync(resolvedPath)) {
|
||||
return {
|
||||
resolvedModule: {
|
||||
resolvedFileName: resolvedPath,
|
||||
extension: ext,
|
||||
isExternalLibraryImport: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to standard resolution (for node_modules etc)
|
||||
return ts.resolveModuleName(moduleName, containingFile, options, host)
|
||||
})
|
||||
}
|
||||
|
||||
// 3. Create Program
|
||||
const program = ts.createProgram(entryPoints, compilerOptions, host)
|
||||
const checker = program.getTypeChecker()
|
||||
|
||||
// 4. Visit AST
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (sourceFile.isDeclarationFile) continue
|
||||
ts.forEachChild(sourceFile, node => { visit(node, undefined) })
|
||||
}
|
||||
|
||||
// Extract namespace from useTranslation call
|
||||
function getUseTranslationNamespace (node: ts.CallExpression): string | undefined {
|
||||
const expr = node.expression
|
||||
if (ts.isIdentifier(expr) && expr.text === 'useTranslation') {
|
||||
const arg = node.arguments[0]
|
||||
if (arg && ts.isStringLiteral(arg)) {
|
||||
return arg.text
|
||||
}
|
||||
// Handle array of namespaces: useTranslation(['ns1', 'ns2']) - use the first one
|
||||
if (arg && ts.isArrayLiteralExpression(arg)) {
|
||||
const firstElement = arg.elements[0]
|
||||
if (firstElement && ts.isStringLiteral(firstElement)) {
|
||||
return firstElement.text
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function visit (node: ts.Node, scopeNs: string | undefined) {
|
||||
// Track useTranslation namespace in current scope
|
||||
let currentScopeNs = scopeNs
|
||||
|
||||
// Check if this is a function with useTranslation
|
||||
if (ts.isFunctionDeclaration(node) ||
|
||||
ts.isFunctionExpression(node) ||
|
||||
ts.isArrowFunction(node) ||
|
||||
ts.isMethodDeclaration(node)) {
|
||||
const body = 'body' in node ? node.body : undefined
|
||||
if (body) {
|
||||
// Look for useTranslation in this function
|
||||
const searchForUseTranslation = (n: ts.Node): string | undefined => {
|
||||
if (ts.isVariableDeclaration(n) && n.initializer &&
|
||||
ts.isCallExpression(n.initializer)) {
|
||||
const ns = getUseTranslationNamespace(n.initializer)
|
||||
if (ns) return ns
|
||||
}
|
||||
if (ts.isCallExpression(n)) {
|
||||
const ns = getUseTranslationNamespace(n)
|
||||
if (ns) return ns
|
||||
}
|
||||
let result: string | undefined
|
||||
ts.forEachChild(n, child => {
|
||||
if (!result) result = searchForUseTranslation(child)
|
||||
})
|
||||
return result
|
||||
}
|
||||
currentScopeNs = searchForUseTranslation(body) ?? scopeNs
|
||||
}
|
||||
}
|
||||
|
||||
if (ts.isCallExpression(node) && isTranslationFunction(node)) {
|
||||
const arg = node.arguments[0]
|
||||
if (arg) {
|
||||
if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg)) {
|
||||
ts.forEachChild(node, child => { visit(child, currentScopeNs) })
|
||||
return
|
||||
}
|
||||
|
||||
let values: string[] = []
|
||||
|
||||
// Handle function arguments (e.g. t(() => ...)) by checking return type
|
||||
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
||||
const signature = checker.getSignatureFromDeclaration(arg)
|
||||
if (signature) {
|
||||
const returnType = signature.getReturnType()
|
||||
values = extractStringsFromType(returnType)
|
||||
}
|
||||
} else {
|
||||
// Try standard type resolution first
|
||||
const type = checker.getTypeAtLocation(arg)
|
||||
values = extractStringsFromType(type)
|
||||
}
|
||||
|
||||
// Fallback: If type resolution failed (generic string) but it's a TemplateExpression,
|
||||
// try to manually resolve the parts. This helps in test environments where full type inference is flaky.
|
||||
if (values.length === 0 && ts.isTemplateExpression(arg)) {
|
||||
const head = arg.head.text
|
||||
// Only handle simple case: `prefix.${var}`
|
||||
if (arg.templateSpans.length === 1) {
|
||||
const span = arg.templateSpans[0]
|
||||
const spanType = checker.getTypeAtLocation(span.expression)
|
||||
const spanValues = extractStringsFromType(spanType)
|
||||
|
||||
if (spanValues.length > 0) {
|
||||
values = spanValues.map(v => head + v + span.literal.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract namespace from options (second argument)
|
||||
let optionsNs: string | undefined
|
||||
const optionsArg = node.arguments[1]
|
||||
if (optionsArg && ts.isObjectLiteralExpression(optionsArg)) {
|
||||
const nsProp = optionsArg.properties.find(
|
||||
p => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'ns'
|
||||
)
|
||||
if (nsProp && ts.isPropertyAssignment(nsProp)) {
|
||||
if (ts.isStringLiteral(nsProp.initializer)) {
|
||||
optionsNs = nsProp.initializer.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
values.forEach(val => {
|
||||
let key = val
|
||||
// Priority: 1. options ns, 2. key contains ns, 3. useTranslation ns, 4. default 'all'
|
||||
let ns = optionsNs
|
||||
|
||||
// Check if key contains namespace separator (e.g., "namespace:key")
|
||||
const separatorIndex = val.indexOf(':')
|
||||
if (separatorIndex > 0) {
|
||||
ns = val.substring(0, separatorIndex)
|
||||
key = val.substring(separatorIndex + 1)
|
||||
}
|
||||
|
||||
let uniqueKey = key
|
||||
|
||||
// Fallback to useTranslation namespace or default
|
||||
if (!ns) {
|
||||
if(currentScopeNs) {
|
||||
ns = currentScopeNs
|
||||
} else if (defaultNS) {
|
||||
ns = defaultNS
|
||||
}
|
||||
}
|
||||
if (ns) {
|
||||
uniqueKey = `${ns}:${key}`
|
||||
}
|
||||
|
||||
if (!keys.has(uniqueKey)) {
|
||||
keys.set(uniqueKey, {
|
||||
key,
|
||||
defaultValue: key,
|
||||
nsIsImplicit: !optionsNs && !val.includes(':') && !currentScopeNs,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, child => { visit(child, currentScopeNs) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,15 +25,8 @@ interface AdminSidebarProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
nameKey: string;
|
||||
href: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
permission?: string;
|
||||
}
|
||||
|
||||
const navigation: NavItem[] = [
|
||||
{ nameKey: 'navigation.dashboard', href: '/admin/dashboard', icon: LayoutDashboard },
|
||||
const navigation = [
|
||||
{ nameKey: 'navigation.dashboard', href: '/admin/dashboard', icon: LayoutDashboard, permission: false },
|
||||
{ nameKey: 'navigation.events', href: '/admin/events', icon: Calendar, permission: 'events.view' },
|
||||
{ nameKey: 'navigation.archives', href: '/admin/archives', icon: Archive, permission: 'archives.view' },
|
||||
{ nameKey: 'admin.analytics', href: '/admin/analytics', icon: BarChart3, permission: 'analytics.view' },
|
||||
@@ -44,7 +37,7 @@ const navigation: NavItem[] = [
|
||||
{ nameKey: 'navigation.backup', href: '/admin/backup', icon: HardDrive, permission: 'backup.view' },
|
||||
{ nameKey: 'navigation.cmsPages', href: '/admin/cms', icon: FileText, permission: 'cms.view' },
|
||||
{ nameKey: 'navigation.users', href: '/admin/users', icon: Users, permission: 'users.view' },
|
||||
];
|
||||
] as const;
|
||||
|
||||
export const AdminSidebar: React.FC<AdminSidebarProps> = ({ isOpen, onClose }) => {
|
||||
const location = useLocation();
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
export const BackupConfiguration: ComponentType<any>;
|
||||
+53
-31
@@ -5,18 +5,11 @@ import {
|
||||
Server,
|
||||
Cloud,
|
||||
HardDrive,
|
||||
Clock,
|
||||
Calendar,
|
||||
Shield,
|
||||
AlertCircle,
|
||||
Info,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Wifi,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Loader2,
|
||||
FolderOpen,
|
||||
Database,
|
||||
Image,
|
||||
FileArchive
|
||||
@@ -24,26 +17,58 @@ import {
|
||||
import { toast } from 'react-toastify';
|
||||
import { Button, Card, Input } from '../common';
|
||||
|
||||
export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
interface BackupFormData {
|
||||
backup_enabled: boolean;
|
||||
backup_destination_type: 'local' | 'rsync' | 's3';
|
||||
backup_destination_path: string;
|
||||
backup_rsync_host: string;
|
||||
backup_rsync_user: string;
|
||||
backup_rsync_path: string;
|
||||
backup_rsync_ssh_key: string;
|
||||
backup_s3_endpoint: string;
|
||||
backup_s3_bucket: string;
|
||||
backup_s3_access_key: string;
|
||||
backup_s3_secret_key: string;
|
||||
backup_s3_region: string;
|
||||
backup_schedule: string;
|
||||
backup_schedule_cron: string;
|
||||
backup_retention_days: number;
|
||||
backup_include_database: boolean;
|
||||
backup_include_photos: boolean;
|
||||
backup_include_archives: boolean;
|
||||
backup_include_thumbnails: boolean;
|
||||
backup_include_temp: boolean;
|
||||
backup_compression: boolean;
|
||||
backup_encryption: boolean;
|
||||
backup_encryption_passphrase: string;
|
||||
}
|
||||
|
||||
interface BackupConfigurationProps {
|
||||
config?: Partial<BackupFormData>;
|
||||
onSave: (data: BackupFormData) => void;
|
||||
isSaving: boolean;
|
||||
}
|
||||
|
||||
export const BackupConfiguration: React.FC<BackupConfigurationProps> = ({ config, onSave, isSaving }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
const destinationTypes = [
|
||||
{
|
||||
id: 'local',
|
||||
id: 'local' as const,
|
||||
name: t('backup.configuration.destinationTypes.local.name'),
|
||||
icon: HardDrive,
|
||||
description: t('backup.configuration.destinationTypes.local.description'),
|
||||
fields: ['backup_destination_path']
|
||||
},
|
||||
{
|
||||
id: 'rsync',
|
||||
id: 'rsync' as const,
|
||||
name: t('backup.configuration.destinationTypes.rsync.name'),
|
||||
icon: Server,
|
||||
description: t('backup.configuration.destinationTypes.rsync.description'),
|
||||
fields: ['backup_rsync_host', 'backup_rsync_user', 'backup_rsync_path', 'backup_rsync_ssh_key']
|
||||
},
|
||||
{
|
||||
id: 's3',
|
||||
id: 's3' as const,
|
||||
name: t('backup.configuration.destinationTypes.s3.name'),
|
||||
icon: Cloud,
|
||||
description: t('backup.configuration.destinationTypes.s3.description'),
|
||||
@@ -57,8 +82,8 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
{ value: 'weekly', label: t('backup.configuration.schedule.options.weekly') },
|
||||
{ value: 'custom', label: t('backup.configuration.schedule.options.custom') }
|
||||
];
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
|
||||
const [formData, setFormData] = useState<BackupFormData>({
|
||||
backup_enabled: false,
|
||||
backup_destination_type: 'local',
|
||||
backup_destination_path: '',
|
||||
@@ -101,33 +126,32 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
const handleChange = <K extends keyof BackupFormData>(field: K, value: BackupFormData[K]) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Validate required fields
|
||||
const destinationType = destinationTypes.find(t => t.id === formData.backup_destination_type);
|
||||
const missingFields = [];
|
||||
|
||||
|
||||
const destinationType = destinationTypes.find(dt => dt.id === formData.backup_destination_type);
|
||||
const missingFields: string[] = [];
|
||||
|
||||
if (formData.backup_enabled && destinationType) {
|
||||
destinationType.fields.forEach(field => {
|
||||
if (!formData[field] && !field.includes('optional')) {
|
||||
if (!formData[field as keyof BackupFormData] && !field.includes('optional')) {
|
||||
missingFields.push(field);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
toast.error(t('backup.configuration.messages.requiredFields'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
onSave(formData);
|
||||
};
|
||||
|
||||
@@ -138,14 +162,12 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
toast.success(t('backup.configuration.messages.connectionSuccess'));
|
||||
} catch (error) {
|
||||
toast.error(t('backup.configuration.messages.connectionFailed') + ': ' + error.message);
|
||||
toast.error(t('backup.configuration.messages.connectionFailed') + ': ' + (error as Error).message);
|
||||
} finally {
|
||||
setTestingConnection(false);
|
||||
}
|
||||
};
|
||||
|
||||
const selectedDestination = destinationTypes.find(t => t.id === formData.backup_destination_type);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Enable/Disable Toggle */}
|
||||
@@ -399,7 +421,7 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
{/* Schedule Configuration */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4">{t('backup.configuration.schedule.title')}</h3>
|
||||
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">
|
||||
@@ -456,7 +478,7 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
{/* Backup Content Selection */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4">{t('backup.configuration.whatToBackup.title')}</h3>
|
||||
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
@@ -527,7 +549,7 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
{/* Advanced Options */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4">{t('backup.configuration.advancedOptions.title')}</h3>
|
||||
|
||||
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
@@ -608,4 +630,4 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
export const BackupDashboard: ComponentType<any>;
|
||||
+76
-26
@@ -8,11 +8,9 @@ import {
|
||||
Clock,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
TrendingUp,
|
||||
Shield,
|
||||
Server,
|
||||
Cloud,
|
||||
Calendar,
|
||||
Play,
|
||||
Loader2,
|
||||
AlertTriangle,
|
||||
@@ -21,7 +19,59 @@ import {
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
import { Card, Button } from '../common';
|
||||
|
||||
const StatCard = ({ icon: Icon, label, value, color = 'blue', subtext }) => (
|
||||
export type HealthStatus = 'excellent' | 'good' | 'warning' | 'critical';
|
||||
export type BackupDestinationType = 's3' | 'rsync' | 'local';
|
||||
|
||||
interface BackupStatistics {
|
||||
total_size?: number;
|
||||
files_processed?: number;
|
||||
database_backed_up?: boolean;
|
||||
photos_backed_up?: number;
|
||||
total_photos?: number;
|
||||
archives_backed_up?: number;
|
||||
photo_count?: number;
|
||||
}
|
||||
|
||||
interface BackupRecord {
|
||||
id: number;
|
||||
status: 'completed' | 'failed' | 'running';
|
||||
backup_type: string;
|
||||
created_at: string;
|
||||
duration_seconds: number;
|
||||
statistics?: BackupStatistics;
|
||||
}
|
||||
|
||||
interface BackupStatus {
|
||||
lastBackup?: BackupRecord;
|
||||
totalBackups?: number;
|
||||
recentBackups?: BackupRecord[];
|
||||
}
|
||||
|
||||
interface BackupConfig {
|
||||
backup_destination_type?: BackupDestinationType;
|
||||
backup_enabled?: boolean;
|
||||
backup_s3_bucket?: string;
|
||||
backup_destination_path?: string;
|
||||
backup_rsync_host?: string;
|
||||
backup_retention_days?: number;
|
||||
}
|
||||
|
||||
interface StatCardProps {
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
label: string;
|
||||
value: string | number;
|
||||
color?: string;
|
||||
subtext?: string;
|
||||
}
|
||||
|
||||
interface BackupDashboardProps {
|
||||
status?: BackupStatus;
|
||||
config?: BackupConfig;
|
||||
onRunBackup: () => void;
|
||||
isBackupRunning: boolean;
|
||||
}
|
||||
|
||||
const StatCard: React.FC<StatCardProps> = ({ icon: Icon, label, value, color = 'blue', subtext }) => (
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
@@ -38,7 +88,7 @@ const StatCard = ({ icon: Icon, label, value, color = 'blue', subtext }) => (
|
||||
</Card>
|
||||
);
|
||||
|
||||
const formatBytes = (bytes) => {
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (!bytes) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
@@ -46,28 +96,34 @@ const formatBytes = (bytes) => {
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }) => {
|
||||
const healthColors: Record<HealthStatus, string> = {
|
||||
excellent: 'green',
|
||||
good: 'blue',
|
||||
warning: 'amber',
|
||||
critical: 'red',
|
||||
};
|
||||
|
||||
export const BackupDashboard: React.FC<BackupDashboardProps> = ({ status, config, onRunBackup, isBackupRunning }) => {
|
||||
const { t } = useTranslation();
|
||||
const lastBackup = status?.lastBackup;
|
||||
const statistics = lastBackup?.statistics || {};
|
||||
const statistics = lastBackup?.statistics ?? {};
|
||||
const isConfigured = config && config.backup_destination_type;
|
||||
const isEnabled = config?.backup_enabled;
|
||||
|
||||
// Calculate backup health score
|
||||
const getHealthScore = () => {
|
||||
const getHealthScore = (): { score: number; status: HealthStatus; message: string } => {
|
||||
if (!lastBackup) return { score: 0, status: 'critical', message: t('backup.dashboard.healthMessages.noBackups') };
|
||||
|
||||
const hoursSinceBackup = (Date.now() - new Date(lastBackup.created_at)) / (1000 * 60 * 60);
|
||||
|
||||
|
||||
const hoursSinceBackup = (Date.now() - new Date(lastBackup.created_at).getTime()) / (1000 * 60 * 60);
|
||||
|
||||
if (lastBackup.status === 'failed') {
|
||||
return { score: 0, status: 'critical', message: t('backup.dashboard.healthMessages.lastBackupFailed') };
|
||||
}
|
||||
|
||||
|
||||
if (hoursSinceBackup < 24) {
|
||||
return { score: 100, status: 'excellent', message: t('backup.dashboard.healthMessages.upToDate') };
|
||||
} else if (hoursSinceBackup < 48) {
|
||||
return { score: 75, status: 'good', message: t('backup.dashboard.healthMessages.recent') };
|
||||
} else if (hoursSinceBackup < 168) { // 1 week
|
||||
} else if (hoursSinceBackup < 168) {
|
||||
return { score: 50, status: 'warning', message: t('backup.dashboard.healthMessages.gettingOld') };
|
||||
} else {
|
||||
return { score: 25, status: 'critical', message: t('backup.dashboard.healthMessages.outdated') };
|
||||
@@ -75,12 +131,6 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
};
|
||||
|
||||
const health = getHealthScore();
|
||||
const healthColors = {
|
||||
excellent: 'green',
|
||||
good: 'blue',
|
||||
warning: 'amber',
|
||||
critical: 'red'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -109,7 +159,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
{t(`backup.dashboard.healthStatus.${health.status}`)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="relative w-24 h-24">
|
||||
<svg className="w-24 h-24 transform -rotate-90">
|
||||
@@ -137,7 +187,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
<span className="text-2xl font-bold text-neutral-900 dark:text-neutral-100">{health.score}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex-1">
|
||||
<p className="text-neutral-700 dark:text-neutral-300 font-medium">{health.message}</p>
|
||||
{lastBackup && (
|
||||
@@ -145,7 +195,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
Last successful backup: {formatDistanceToNow(new Date(lastBackup.created_at), { addSuffix: true })}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
<Button
|
||||
onClick={onRunBackup}
|
||||
disabled={!isConfigured || !isEnabled || isBackupRunning}
|
||||
@@ -177,7 +227,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
color="blue"
|
||||
subtext={lastBackup ? `${t('backup.dashboard.stats.last')}: ${format(new Date(lastBackup.created_at), 'PP')}` : t('backup.dashboard.stats.noBackupsYet')}
|
||||
/>
|
||||
|
||||
|
||||
<StatCard
|
||||
icon={HardDrive}
|
||||
label={t('backup.dashboard.stats.backupSize')}
|
||||
@@ -185,7 +235,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
color="green"
|
||||
subtext={`${statistics.files_processed || 0} ${t('backup.dashboard.stats.files')}`}
|
||||
/>
|
||||
|
||||
|
||||
<StatCard
|
||||
icon={Clock}
|
||||
label={t('backup.dashboard.stats.lastDuration')}
|
||||
@@ -193,7 +243,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
color="purple"
|
||||
subtext={lastBackup ? format(new Date(lastBackup.created_at), 'p') : ''}
|
||||
/>
|
||||
|
||||
|
||||
<StatCard
|
||||
icon={Shield}
|
||||
label={t('backup.dashboard.stats.backupStatus')}
|
||||
@@ -324,4 +374,4 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -19,7 +19,7 @@ const RATING_OPTIONS = [
|
||||
{ value: 3, label: 'filter.threeStarsPlus' },
|
||||
{ value: 4, label: 'filter.fourStarsPlus' },
|
||||
{ value: 5, label: 'filter.fiveStarsOnly' },
|
||||
];
|
||||
] as const;
|
||||
|
||||
export const PhotoFilterPanel: React.FC<PhotoFilterPanelProps> = ({
|
||||
filters,
|
||||
|
||||
+472
-357
@@ -158,7 +158,14 @@
|
||||
"navigation": {
|
||||
"dashboard": "Dashboard",
|
||||
"events": "Veranstaltungen",
|
||||
"settings": "Einstellungen"
|
||||
"settings": "Einstellungen",
|
||||
"archives": "Archive",
|
||||
"emailSettings": "E-Mail-Einstellungen",
|
||||
"branding": "Markenidentität",
|
||||
"eventTypes": "Veranstaltungstypen",
|
||||
"backup": "Backup & Wiederherstellung",
|
||||
"cmsPages": "CMS-Seiten",
|
||||
"users": "Benutzer"
|
||||
},
|
||||
"eventTypes": {
|
||||
"title": "Veranstaltungsarten",
|
||||
@@ -275,6 +282,12 @@
|
||||
"actions": {
|
||||
"runBackupNow": "Backup jetzt ausführen",
|
||||
"running": "Wird ausgeführt..."
|
||||
},
|
||||
"healthStatus": {
|
||||
"warning": "Warnung",
|
||||
"excellent": "Ausgezeichnet",
|
||||
"good": "Gut",
|
||||
"critical": "Kritisch"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
@@ -634,51 +647,51 @@
|
||||
"anonymous": "Anonym"
|
||||
},
|
||||
"favorites": "Favoriten",
|
||||
"expiresIn_one": "",
|
||||
"expiresIn_other": "",
|
||||
"downloading_one": "",
|
||||
"downloading_other": "",
|
||||
"photosSelected_one": "",
|
||||
"photosSelected_other": "",
|
||||
"downloadSelected_one": "",
|
||||
"downloadSelected_other": "",
|
||||
"expiresIn_one": "Galerie läuft in {{count}} Tag ab",
|
||||
"expiresIn_other": "Galerie läuft in {{count}} Tagen ab",
|
||||
"downloading_one": "1 Foto wird heruntergeladen…",
|
||||
"downloading_other": "{{count}} Fotos werden heruntergeladen…",
|
||||
"photosSelected_one": "{{count}} Foto ausgewählt",
|
||||
"photosSelected_other": "{{count}} Fotos ausgewählt",
|
||||
"downloadSelected_one": "{{count}} Foto herunterladen",
|
||||
"downloadSelected_other": "{{count}} Fotos herunterladen",
|
||||
"guestRecovery": {
|
||||
"invalidEmail": "",
|
||||
"codeSent": "",
|
||||
"requestError": "",
|
||||
"invalidCode": "",
|
||||
"verifyError": "",
|
||||
"back": "",
|
||||
"title": "",
|
||||
"emailStepDescription": "",
|
||||
"codeStepDescription": "",
|
||||
"emailLabel": "",
|
||||
"sendCode": "",
|
||||
"codeLabel": "",
|
||||
"verifyCode": ""
|
||||
"invalidEmail": "Gültige E-Mail-Adresse eingeben",
|
||||
"codeSent": "Prüfen Sie Ihren Posteingang auf einen Bestätigungscode.",
|
||||
"requestError": "Code konnte nicht gesendet werden. Bitte erneut versuchen.",
|
||||
"invalidCode": "6-stelligen Code eingeben",
|
||||
"verifyError": "Ungültiger oder abgelaufener Code.",
|
||||
"back": "Zurück",
|
||||
"title": "Ihre Auswahl wiederherstellen",
|
||||
"emailStepDescription": "Geben Sie die E-Mail-Adresse ein, die Sie zuvor verwendet haben. Wir senden Ihnen einen 6-stelligen Bestätigungscode.",
|
||||
"codeStepDescription": "Geben Sie den 6-stelligen Code ein, den wir an Ihre E-Mail-Adresse gesendet haben.",
|
||||
"emailLabel": "E-Mail",
|
||||
"sendCode": "Code senden",
|
||||
"codeLabel": "Bestätigungscode",
|
||||
"verifyCode": "Verifizieren und fortfahren"
|
||||
},
|
||||
"guestPrompt": {
|
||||
"nameRequired": "",
|
||||
"invalidEmail": "",
|
||||
"emailRequired": "",
|
||||
"error": "",
|
||||
"title": "",
|
||||
"description": "",
|
||||
"nameLabel": "",
|
||||
"namePlaceholder": "",
|
||||
"emailLabelRequired": "",
|
||||
"emailLabel": "",
|
||||
"emailPlaceholder": "",
|
||||
"submit": "",
|
||||
"alreadyHere": ""
|
||||
"nameRequired": "Name ist erforderlich",
|
||||
"invalidEmail": "Ungültige E-Mail-Adresse",
|
||||
"emailRequired": "E-Mail ist erforderlich",
|
||||
"error": "Registrierung fehlgeschlagen",
|
||||
"title": "Willkommen — wie heißen Sie?",
|
||||
"description": "Ihre Auswahl wird unter diesem Namen gespeichert, damit der Fotograf weiß, welche Fotos Ihnen gefallen.",
|
||||
"nameLabel": "Ihr Name",
|
||||
"namePlaceholder": "Name eingeben",
|
||||
"emailLabelRequired": "E-Mail",
|
||||
"emailLabel": "E-Mail (optional)",
|
||||
"emailPlaceholder": "Sie@beispiel.de",
|
||||
"submit": "Weiter",
|
||||
"alreadyHere": "Ich war schon einmal hier"
|
||||
},
|
||||
"footer": {
|
||||
"forgetMeConfirm": "",
|
||||
"forgetMe": ""
|
||||
"forgetMeConfirm": "Ihr Name und Ihre Auswahl werden aus dieser Galerie entfernt.",
|
||||
"forgetMe": "Vergiss mich ({{name}})"
|
||||
},
|
||||
"photosCount_one": "",
|
||||
"photosCount_other": "",
|
||||
"poweredBy": ""
|
||||
"photosCount_one": "{{count}} Foto",
|
||||
"photosCount_other": "{{count}} Fotos",
|
||||
"poweredBy": "Bereitgestellt von PicPeak"
|
||||
},
|
||||
"categories": {
|
||||
"title": "Fotokategorien",
|
||||
@@ -907,14 +920,14 @@
|
||||
"successAll": "{{count}} Veranstaltungen endgültig gelöscht",
|
||||
"successPartial": "{{success}} Veranstaltungen gelöscht, {{failed}} fehlgeschlagen",
|
||||
"errorGeneric": "Veranstaltungen konnten nicht gelöscht werden",
|
||||
"successAll_one": "",
|
||||
"successAll_other": "",
|
||||
"title_one": "",
|
||||
"title_other": "",
|
||||
"processing_one": "",
|
||||
"processing_other": "",
|
||||
"submit_one": "",
|
||||
"submit_other": ""
|
||||
"successAll_one": "{{count}} Veranstaltung dauerhaft gelöscht",
|
||||
"successAll_other": "{{count}} Veranstaltungen dauerhaft gelöscht",
|
||||
"title_one": "{{count}} Veranstaltung dauerhaft löschen?",
|
||||
"title_other": "{{count}} Veranstaltungen dauerhaft löschen?",
|
||||
"processing_one": "{{count}} Veranstaltung wird gelöscht. Dies kann einige Minuten dauern — bitte schließen Sie dieses Fenster nicht.",
|
||||
"processing_other": "{{count}} Veranstaltungen werden gelöscht. Dies kann einige Minuten dauern — bitte schließen Sie dieses Fenster nicht.",
|
||||
"submit_one": "{{count}} Veranstaltung löschen",
|
||||
"submit_other": "{{count}} Veranstaltungen löschen"
|
||||
},
|
||||
"searchEventsPlaceholder": "Veranstaltungen suchen...",
|
||||
"all": "Alle",
|
||||
@@ -941,21 +954,21 @@
|
||||
"complete": "Fertig!",
|
||||
"failed": "Umbenennung fehlgeschlagen",
|
||||
"confirm": "Veranstaltung umbenennen",
|
||||
"success": "",
|
||||
"filesRenamed_one": "",
|
||||
"filesRenamed_other": "",
|
||||
"newLink": "",
|
||||
"currentName": "",
|
||||
"newName": "",
|
||||
"enterNewName": "",
|
||||
"newUrl": "",
|
||||
"checkingAvailability": "",
|
||||
"resendEmail": "",
|
||||
"emailTo": "",
|
||||
"warningTitle": "",
|
||||
"warning1": "",
|
||||
"warning2": "",
|
||||
"warning3": ""
|
||||
"success": "Veranstaltung erfolgreich umbenannt!",
|
||||
"filesRenamed_one": "{{count}} Datei aktualisiert",
|
||||
"filesRenamed_other": "{{count}} Dateien aktualisiert",
|
||||
"newLink": "Neuer Galerie-Link",
|
||||
"currentName": "Aktueller Name:",
|
||||
"newName": "Neuer Veranstaltungsname",
|
||||
"enterNewName": "Neuen Veranstaltungsnamen eingeben",
|
||||
"newUrl": "Neue URL:",
|
||||
"checkingAvailability": "Verfügbarkeit wird geprüft...",
|
||||
"resendEmail": "Einladungs-E-Mail mit neuem Galerie-Link erneut senden",
|
||||
"emailTo": "Aktualisierte Galerie-Zugangsemail senden an",
|
||||
"warningTitle": "Bitte beachten:",
|
||||
"warning1": "Die Galerie-URL wird sich ändern",
|
||||
"warning2": "Alte URLs werden automatisch zur neuen URL weitergeleitet",
|
||||
"warning3": "Fotodateien können umbenannt werden"
|
||||
},
|
||||
"stats": {
|
||||
"totalEvents": "Gesamtveranstaltungen",
|
||||
@@ -972,27 +985,27 @@
|
||||
"downloadingArchive": "{{name}}-Archiv wird heruntergeladen...",
|
||||
"downloadStarted": "Download gestartet",
|
||||
"failedToDownloadArchive": "Archiv-Download fehlgeschlagen",
|
||||
"bulkArchiveSuccess_one": "",
|
||||
"bulkArchiveSuccess_other": "",
|
||||
"daysLeft_one": "",
|
||||
"daysLeft_other": "",
|
||||
"eventsSelected_one": "",
|
||||
"eventsSelected_other": "",
|
||||
"paginationLabel": "",
|
||||
"filtered": "",
|
||||
"pageOf": "",
|
||||
"notFound": "",
|
||||
"customerPhone": "",
|
||||
"customerPhonePlaceholder": "",
|
||||
"allowPresignedDownload": "",
|
||||
"neverExpires": "",
|
||||
"rightClickBlocked": "",
|
||||
"devtoolsDetection": "",
|
||||
"watermarked": "",
|
||||
"importExternal": "",
|
||||
"externalImportInfo": "",
|
||||
"selectExternalFolder": "",
|
||||
"importFromSelectedFolder": ""
|
||||
"bulkArchiveSuccess_one": "{{count}} Veranstaltung erfolgreich archiviert",
|
||||
"bulkArchiveSuccess_other": "{{count}} Veranstaltungen erfolgreich archiviert",
|
||||
"daysLeft_one": "({{count}} Tag verbleibend)",
|
||||
"daysLeft_other": "({{count}} Tage verbleibend)",
|
||||
"eventsSelected_one": "{{count}} Veranstaltung ausgewählt",
|
||||
"eventsSelected_other": "{{count}} Veranstaltungen ausgewählt",
|
||||
"paginationLabel": "{{from}}–{{to}} von {{total}}",
|
||||
"filtered": "gefiltert",
|
||||
"pageOf": "Seite {{page}} von {{totalPages}}",
|
||||
"notFound": "Veranstaltung nicht gefunden",
|
||||
"customerPhone": "Kundentelefon",
|
||||
"customerPhonePlaceholder": "+49 555 555 1234",
|
||||
"allowPresignedDownload": "Direkten S3-Download erlauben (kein Wasserzeichen, nur S3-Modus)",
|
||||
"neverExpires": "Nie",
|
||||
"rightClickBlocked": "Rechtsklick gesperrt",
|
||||
"devtoolsDetection": "DevTools-Erkennung",
|
||||
"watermarked": "Mit Wasserzeichen",
|
||||
"importExternal": "Aus externem Ordner importieren",
|
||||
"externalImportInfo": "Alle Bilder aus dem ausgewählten Ordner werden importiert.",
|
||||
"selectExternalFolder": "Externen Ordner unter /external-media auswählen",
|
||||
"importFromSelectedFolder": "Aus ausgewähltem Ordner importieren"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Systemeinstellungen",
|
||||
@@ -1139,12 +1152,12 @@
|
||||
"saveSettings": "Veranstaltungseinstellungen speichern",
|
||||
"noteTitle": "Hinweis",
|
||||
"noteText": "Diese Einstellungen betreffen nur die Erstellung neuer Veranstaltungen. Bestehende Veranstaltungen sind nicht betroffen. Standardmäßig sind alle Felder erforderlich.",
|
||||
"defaultRequirePassword": "",
|
||||
"defaultRequirePasswordHelp": "",
|
||||
"showGalleryFilterBar": "",
|
||||
"showGalleryFilterBarHelp": "",
|
||||
"enablePhoneField": "",
|
||||
"enablePhoneFieldHelp": ""
|
||||
"defaultRequirePassword": "Standardmäßig Passwort erforderlich",
|
||||
"defaultRequirePasswordHelp": "\"Passwort erforderlich\" beim Erstellen neuer Veranstaltungen vorausgewählt. Deaktivieren für schnellere Erstellung öffentlicher Galerien.",
|
||||
"showGalleryFilterBar": "Filterleiste in Galerien anzeigen",
|
||||
"showGalleryFilterBarHelp": "Dateiname-Suche und Sortiersteuerungen über Raster-Galerien anzeigen. Deaktivieren für ein übersichtlicheres Layout.",
|
||||
"enablePhoneField": "Telefonnummer-Feld aktivieren",
|
||||
"enablePhoneFieldHelp": "Fügt ein optionales Telefonnummer-Eingabefeld zum Veranstaltungsformular hinzu. Nützlich für Folgeautomatisierungen wie WhatsApp-Zustellung via n8n. Immer optional, auch wenn aktiviert."
|
||||
},
|
||||
"imageSecurity": {
|
||||
"title": "Bildschutz",
|
||||
@@ -1284,8 +1297,8 @@
|
||||
"checkSuccess": "Benachrichtigung für neue Version gesendet",
|
||||
"checkNoAction": "Keine Benachrichtigung erforderlich: {{reason}}",
|
||||
"checkError": "Fehler beim Prüfen auf Updates",
|
||||
"emailSent_one": "",
|
||||
"emailSent_other": ""
|
||||
"emailSent_one": "Benachrichtigungs-E-Mail an {{count}} Empfänger gesendet",
|
||||
"emailSent_other": "Benachrichtigungs-E-Mail an {{count}} Empfänger gesendet"
|
||||
},
|
||||
"moderation": {
|
||||
"title": "Moderation",
|
||||
@@ -1344,52 +1357,52 @@
|
||||
"privacyText": "IP-Adressen werden aus Datenschutzgründen gehasht. Es werden keine persönlichen Daten gespeichert. Analysedaten werden 90 Tage aufbewahrt."
|
||||
},
|
||||
"groups": {
|
||||
"general": "",
|
||||
"display": "",
|
||||
"privacySecurity": "",
|
||||
"integrations": "",
|
||||
"system": ""
|
||||
"general": "Allgemein",
|
||||
"display": "Anzeige",
|
||||
"privacySecurity": "Datenschutz & Sicherheit",
|
||||
"integrations": "Integrationen",
|
||||
"system": "System"
|
||||
},
|
||||
"apiTokens": {
|
||||
"title": "",
|
||||
"createError": "",
|
||||
"revoked": "",
|
||||
"subtitle": "",
|
||||
"copyNow": "",
|
||||
"copied": "",
|
||||
"copyFailed": "",
|
||||
"name": "",
|
||||
"namePlaceholder": "",
|
||||
"scopes": "",
|
||||
"generate": "",
|
||||
"scopeHint": "",
|
||||
"existing": "",
|
||||
"lastUsed": "",
|
||||
"created": "",
|
||||
"status": "",
|
||||
"statusRevoked": "",
|
||||
"statusExpired": "",
|
||||
"statusActive": "",
|
||||
"confirmRevoke": "",
|
||||
"revoke": "",
|
||||
"empty": ""
|
||||
"title": "API-Tokens",
|
||||
"createError": "Token konnte nicht erstellt werden",
|
||||
"revoked": "Token widerrufen",
|
||||
"subtitle": "Langlebige Bearer-Tokens für die öffentliche /api/v1-Oberfläche — n8n-Integrationen, eigene Apps, Skripte. Tokens agieren als der Admin-Benutzer, der sie erstellt hat, eingeschränkt auf die gewählten Berechtigungen.",
|
||||
"copyNow": "Kopieren Sie diesen Token jetzt — er wird nicht erneut angezeigt.",
|
||||
"copied": "Kopiert",
|
||||
"copyFailed": "Kopieren fehlgeschlagen",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "z.B. n8n Produktion",
|
||||
"scopes": "Berechtigungen",
|
||||
"generate": "Token generieren",
|
||||
"scopeHint": "admin > write > read. Ein reiner Lese-Token kann keine Änderungen vornehmen, auch wenn der Besitzer super_admin ist.",
|
||||
"existing": "Vorhandene Tokens",
|
||||
"lastUsed": "Zuletzt verwendet",
|
||||
"created": "Erstellt",
|
||||
"status": "Status",
|
||||
"statusRevoked": "Widerrufen",
|
||||
"statusExpired": "Abgelaufen",
|
||||
"statusActive": "Aktiv",
|
||||
"confirmRevoke": "Diesen Token wirklich widerrufen?",
|
||||
"revoke": "Widerrufen",
|
||||
"empty": "Noch keine Tokens. Generieren Sie oben einen, um zu beginnen."
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"piiNotice": "",
|
||||
"copyNow": "",
|
||||
"name": "",
|
||||
"url": "",
|
||||
"events": "",
|
||||
"filter": "",
|
||||
"template": "",
|
||||
"create": "",
|
||||
"existing": "",
|
||||
"empty": ""
|
||||
"title": "Webhooks",
|
||||
"subtitle": "Senden Sie POST-Ereignisbenachrichtigungen an Ihre URL, sobald etwas passiert — Galerie veröffentlicht, Foto hochgeladen, Veranstaltung archiviert usw. Signiert mit HMAC-SHA256 im X-PicPeak-Signature-Header.",
|
||||
"piiNotice": "event.*-Payloads enthalten Kundenkontaktdaten (Name, E-Mail, Telefon) und den Galerie-Share-Token, sofern gespeichert. Richten Sie Webhooks nur auf vertrauenswürdige Empfänger — sie haben alles, um den Kunden zu kontaktieren oder die Galerie zu öffnen.",
|
||||
"copyNow": "Kopieren Sie dieses Signaturgeheimnis jetzt — es wird nicht erneut angezeigt.",
|
||||
"name": "Name",
|
||||
"url": "Empfänger-URL",
|
||||
"events": "Ereignisse abonnieren",
|
||||
"filter": "Filter (JSON, optional)",
|
||||
"template": "Vorlage (optional)",
|
||||
"create": "Webhook erstellen",
|
||||
"existing": "Vorhandene Webhooks",
|
||||
"empty": "Noch keine Webhooks. Erstellen Sie oben einen, um Ereignisbenachrichtigungen zu erhalten."
|
||||
},
|
||||
"sectionLabel": "",
|
||||
"navAriaLabel": ""
|
||||
"sectionLabel": "Einstellungsbereich",
|
||||
"navAriaLabel": "Einstellungsnavigation"
|
||||
},
|
||||
"branding": {
|
||||
"title": "Branding & Themen",
|
||||
@@ -1588,38 +1601,68 @@
|
||||
"betaThumbnailWarningTitle": "Niedrige Thumbnail-Auflösung erkannt",
|
||||
"betaThumbnailWarningText": "Ihre Thumbnails sind derzeit {{width}}×{{height}}px. Beta-Themes zeigen Fotos in größeren Formaten an und benötigen mindestens {{recommended}}×{{recommended}}px für gute Qualität. Erhöhen Sie die Thumbnail-Abmessungen unter Einstellungen > Thumbnails und regenerieren Sie diese.",
|
||||
"betaThumbnailWarningLink": "Zu den Thumbnail-Einstellungen",
|
||||
"logoSize": "",
|
||||
"surfaceColor": "",
|
||||
"elevatedColor": "",
|
||||
"borderColor": "",
|
||||
"mutedTextColor": "",
|
||||
"accentDarkColor": "",
|
||||
"syncFromBranding": "",
|
||||
"forceColorMode": "",
|
||||
"forceColorModeHelp": "",
|
||||
"forceColorModeNone": "",
|
||||
"forceColorModeDark": "",
|
||||
"forceColorModeLight": "",
|
||||
"colorGroupSurfaces": "",
|
||||
"colorGroupSurfacesHelp": "",
|
||||
"backgroundColorHelp": "",
|
||||
"surfaceColorHelp": "",
|
||||
"elevatedColorHelp": "",
|
||||
"borderColorHelp": "",
|
||||
"colorGroupText": "",
|
||||
"colorGroupTextHelp": "",
|
||||
"textColorHelp": "",
|
||||
"mutedTextColorHelp": "",
|
||||
"colorGroupAccent": "",
|
||||
"colorGroupAccentHelp": "",
|
||||
"accentColorHelp": "",
|
||||
"accentDarkColorHelp": "",
|
||||
"cssTemplate": "",
|
||||
"cssTemplateDescription": "",
|
||||
"noTemplate": "",
|
||||
"noTemplateDescription": "",
|
||||
"templateSlot": "",
|
||||
"eventCustomCSS": ""
|
||||
"logoSize": "Logo-Größe",
|
||||
"surfaceColor": "Oberfläche",
|
||||
"elevatedColor": "Erhöht",
|
||||
"borderColor": "Rahmen",
|
||||
"mutedTextColor": "Gedämpfter Text",
|
||||
"accentDarkColor": "Akzent (gefüllt)",
|
||||
"syncFromBranding": "Vom Branding synchronisieren",
|
||||
"forceColorMode": "Farbmodus erzwingen",
|
||||
"forceColorModeHelp": "Sperrt die gesamte Admin- und öffentliche Website auf Dunkel- oder Hellmodus. Die Dunkel-/Hell-Umschalttaste wird ausgeblendet, wenn eine Sperre aktiv ist. Veranstaltungsthemen, die den Farbmodus überschreiben möchten, werden ebenfalls gezwungen, diesem zu folgen.",
|
||||
"forceColorModeNone": "Kein Zwang (Benutzerauswahl)",
|
||||
"forceColorModeDark": "Dunkelmodus erzwingen",
|
||||
"forceColorModeLight": "Hellmodus erzwingen",
|
||||
"colorGroupSurfaces": "Oberflächen",
|
||||
"colorGroupSurfacesHelp": "Die neutralen Ebenen hinter Ihrem Inhalt. Hintergrund liegt am weitesten zurück; Oberfläche und Erhöht stapeln sich darüber.",
|
||||
"backgroundColorHelp": "Die Seite selbst — Hintergrundfarbe jeder Galerie, Admin-Seite und CMS-Seite.",
|
||||
"surfaceColorHelp": "Karten, Seitenleiste, Kopfzeile und Navigation. Die erste Ebene über dem Hintergrund.",
|
||||
"elevatedColorHelp": "Panels, die über Karten schweben: Bild-Platzhalter, Hover-/Aktiv-Zeilen, Modal-Kopfzeilen, Code-Blöcke.",
|
||||
"borderColorHelp": "Trennlinien, Tabellengitterlinien, Kartenumrisse, Eingabe-Rahmen.",
|
||||
"colorGroupText": "Text",
|
||||
"colorGroupTextHelp": "Vordergrund-Textfarben. Primär für alles, worauf Leser sich konzentrieren; Sekundär für unterstützenden Text.",
|
||||
"textColorHelp": "Überschriften, Fließtext, Tabellenzellen, Formulareingabewerte, Navigationsbezeichnungen — die Haupttextfarbe.",
|
||||
"mutedTextColorHelp": "Bildunterschriften, Hilfetext unter Eingaben, Tabellen-Spaltenüberschriften, Fußzeilenlinks, Daten und Metadaten.",
|
||||
"colorGroupAccent": "Akzent",
|
||||
"colorGroupAccentHelp": "Markenfarben, die interaktive Elemente hervorheben. Verwenden Sie ein starkes Farbpaar — Akzent für Umrisse/Text, Akzent Dunkel für gefüllte Buttons.",
|
||||
"accentColorHelp": "Links, Symbole, Fokusringe, Hover-Zustände bei Primär-Buttons, aktive Seitenleisten-Unterstreichung. Sollte auf Hintergrund und Oberfläche gut lesbar sein.",
|
||||
"accentDarkColorHelp": "Gefüllte CTA-Buttons, aktiver Seitenleisten-Eintrag-Hintergrund, Abzeichen und Tags. Muss genug Kontrast für weißen Text bieten.",
|
||||
"cssTemplate": "CSS-Vorlage",
|
||||
"cssTemplateDescription": "Wählen Sie eine vorgefertigte CSS-Vorlage, um dieser Galerie ein anwendungsweites Styling zu geben. Vorlagen können unter Einstellungen > CSS-Vorlagen verwaltet werden.",
|
||||
"noTemplate": "Keine Vorlage",
|
||||
"noTemplateDescription": "Nur Theme-Einstellungen ohne CSS-Vorlage verwenden",
|
||||
"templateSlot": "Slot {{slot}}",
|
||||
"eventCustomCSS": "Veranstaltungsspezifisches benutzerdefiniertes CSS",
|
||||
"layoutDescriptions": {
|
||||
"grid": "Klassisches Raster mit einheitlichen Fotogrößen",
|
||||
"masonry": "Pinterest-Stil mit variablen Höhen",
|
||||
"carousel": "Vollbild-Diashow mit Navigation",
|
||||
"timeline": "Nach Datum geordnete Fotos",
|
||||
"mosaic": "Künstlerisches Layout mit gemischten Größen",
|
||||
"gallery-premium": "Elegantes helles Thema mit Hero und Masonry (Beta)",
|
||||
"gallery-story": "Cinematisches dunkles Thema mit Szenenabschnitten (Beta)"
|
||||
},
|
||||
"headerStyleOptions": {
|
||||
"hero": "Hero-Bild",
|
||||
"standard": "Standard",
|
||||
"banner": "Banner",
|
||||
"minimal": "Minimal",
|
||||
"none": "Kein Header"
|
||||
},
|
||||
"headerStyleDescriptions": {
|
||||
"hero": "Vollhöhenbild mit Veranstaltungsinfo-Overlay",
|
||||
"standard": "Kompakter Header mit Veranstaltungsdetails",
|
||||
"banner": "Standard-Header plus farbiges Banner darüber",
|
||||
"minimal": "Kompakter Header mit wesentlichen Infos",
|
||||
"none": "Header vollständig ausblenden"
|
||||
},
|
||||
"dividerOptions": {
|
||||
"none": "Keiner",
|
||||
"wave": "Welle",
|
||||
"straight": "Gerade",
|
||||
"angle": "Winkel",
|
||||
"curve": "Kurve"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "Admin-Panel",
|
||||
@@ -1635,7 +1678,10 @@
|
||||
"archivedEvents": "Archivierte Veranstaltungen",
|
||||
"systemHealth": "Systemstatus",
|
||||
"health": {
|
||||
"checking": "Prüfe..."
|
||||
"checking": "Prüfe...",
|
||||
"healthy": "Gesund",
|
||||
"warning": "Warnung",
|
||||
"error": "Fehler"
|
||||
},
|
||||
"updates": {
|
||||
"available": "Update verfügbar",
|
||||
@@ -1694,17 +1740,17 @@
|
||||
"archiveRestored": "Archiv für \"{{eventName}}\" wiederhergestellt",
|
||||
"systemActivity": "Systemaktivität: {{type}}",
|
||||
"adminProfileUpdated": "Admin-Profil aktualisiert von {{actorName}}",
|
||||
"photosUploaded_one": "",
|
||||
"photosUploaded_other": "",
|
||||
"photosBulkDeleted_one": "",
|
||||
"photosBulkDeleted_other": "",
|
||||
"bulkDownload_one": "",
|
||||
"bulkDownload_other": ""
|
||||
"photosUploaded_one": "{{count}} Foto in \"{{eventName}}\" hochgeladen",
|
||||
"photosUploaded_other": "{{count}} Fotos in \"{{eventName}}\" hochgeladen",
|
||||
"photosBulkDeleted_one": "{{count}} Foto aus \"{{eventName}}\" gelöscht",
|
||||
"photosBulkDeleted_other": "{{count}} Fotos aus \"{{eventName}}\" gelöscht",
|
||||
"bulkDownload_one": "{{count}} Foto aus \"{{eventName}}\" heruntergeladen",
|
||||
"bulkDownload_other": "{{count}} Fotos aus \"{{eventName}}\" heruntergeladen"
|
||||
},
|
||||
"notificationToasts": {
|
||||
"markedAllRead": "Alle Benachrichtigungen als gelesen markiert",
|
||||
"clearedAll_one": "",
|
||||
"clearedAll_other": ""
|
||||
"clearedAll_one": "{{count}} Benachrichtigung gelöscht",
|
||||
"clearedAll_other": "{{count}} Benachrichtigungen gelöscht"
|
||||
},
|
||||
"changePassword": "Passwort ändern",
|
||||
"loadingDashboard": "Dashboard wird geladen...",
|
||||
@@ -1724,73 +1770,135 @@
|
||||
"showSelected": "Einblenden",
|
||||
"hiddenSuccess": "Fotos für Gäste ausgeblendet",
|
||||
"visibleSuccess": "Fotos jetzt für Gäste sichtbar",
|
||||
"processingStatus": "",
|
||||
"processingFailed": "",
|
||||
"retryQueued": ""
|
||||
"processingStatus": "Wird verarbeitet…",
|
||||
"processingFailed": "Fehlgeschlagen",
|
||||
"retryQueued": "Wiederholung in Warteschlange"
|
||||
},
|
||||
"events": {
|
||||
"tabs": {
|
||||
"guests": ""
|
||||
"guests": "Gäste"
|
||||
}
|
||||
},
|
||||
"daysLeft_one": "",
|
||||
"daysLeft_other": "",
|
||||
"viewAllExpiringEvents_one": "",
|
||||
"viewAllExpiringEvents_other": "",
|
||||
"daysLeft_one": "{{count}} Tag verbleibend",
|
||||
"daysLeft_other": "{{count}} Tage verbleibend",
|
||||
"viewAllExpiringEvents_one": "Alle {{count}} ablaufenden Veranstaltungen anzeigen",
|
||||
"viewAllExpiringEvents_other": "Alle {{count}} ablaufenden Veranstaltungen anzeigen",
|
||||
"guests": {
|
||||
"loading": "",
|
||||
"loading": "Wird geladen...",
|
||||
"aggregate": {
|
||||
"empty": "",
|
||||
"description": ""
|
||||
"empty": "Noch keine Gastauswahl.",
|
||||
"description": "Fotos sortiert nach der Anzahl der Gäste, die sie geliket oder favorisiert haben."
|
||||
},
|
||||
"inviteCreated": "",
|
||||
"inviteCreateError": "",
|
||||
"inviteRevoked": "",
|
||||
"inviteRevokeError": "",
|
||||
"invitesTitle": "",
|
||||
"createInvite": "",
|
||||
"inviteName": "",
|
||||
"inviteEmail": "",
|
||||
"generateInvite": "",
|
||||
"existingInvites": "",
|
||||
"noInvites": "",
|
||||
"copyLink": "",
|
||||
"revokeInvite": "",
|
||||
"deletedToast": "",
|
||||
"deletedError": "",
|
||||
"mergedToast": "",
|
||||
"mergedError": "",
|
||||
"forgetGuestConfirm": "",
|
||||
"exportError": "",
|
||||
"mergeSelectAtLeastTwo": "",
|
||||
"mergeConfirm_one": "",
|
||||
"mergeConfirm_other": "",
|
||||
"backToList": "",
|
||||
"title": "",
|
||||
"mergeSelected_one": "",
|
||||
"mergeSelected_other": "",
|
||||
"mergeNow": "",
|
||||
"aggregateView": "",
|
||||
"mergeMode": "",
|
||||
"exportAll": "",
|
||||
"empty": "",
|
||||
"inviteCreated": "Einladung erstellt",
|
||||
"inviteCreateError": "Einladung konnte nicht erstellt werden",
|
||||
"inviteRevoked": "Einladung widerrufen",
|
||||
"inviteRevokeError": "Einladung konnte nicht widerrufen werden",
|
||||
"invitesTitle": "Gasteinladungen",
|
||||
"createInvite": "Einladung erstellen",
|
||||
"inviteName": "Gastname",
|
||||
"inviteEmail": "E-Mail (optional)",
|
||||
"generateInvite": "Einladungslink generieren",
|
||||
"existingInvites": "Vorhandene Einladungen",
|
||||
"noInvites": "Noch keine Einladungen",
|
||||
"copyLink": "Link kopieren",
|
||||
"revokeInvite": "Widerrufen",
|
||||
"deletedToast": "Gast entfernt",
|
||||
"deletedError": "Gast konnte nicht entfernt werden",
|
||||
"mergedToast": "Gäste zusammengeführt",
|
||||
"mergedError": "Gäste konnten nicht zusammengeführt werden",
|
||||
"forgetGuestConfirm": "Diesen Gast entfernen? Ihre Auswahl wird anonymisiert, aber in den Gesamtzahlen behalten.",
|
||||
"exportError": "Export fehlgeschlagen",
|
||||
"mergeSelectAtLeastTwo": "Mindestens 2 Gäste zum Zusammenführen auswählen",
|
||||
"mergeConfirm_one": "{{count}} Gast in {{name}} zusammenführen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"mergeConfirm_other": "{{count}} Gäste in {{name}} zusammenführen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"backToList": "Zurück zur Liste",
|
||||
"title": "Gäste",
|
||||
"mergeSelected_one": "{{count}} ausgewählt",
|
||||
"mergeSelected_other": "{{count}} ausgewählt",
|
||||
"mergeNow": "Auswahl zusammenführen",
|
||||
"aggregateView": "Nach Beliebtheit",
|
||||
"mergeMode": "Zusammenführen",
|
||||
"exportAll": "Alle exportieren",
|
||||
"empty": "Noch keine Gäste registriert.",
|
||||
"columns": {
|
||||
"name": "",
|
||||
"email": "",
|
||||
"likes": "",
|
||||
"favorites": "",
|
||||
"comments": "",
|
||||
"ratings": "",
|
||||
"lastSeen": ""
|
||||
"name": "Name",
|
||||
"email": "E-Mail",
|
||||
"likes": "Likes",
|
||||
"favorites": "Favoriten",
|
||||
"comments": "Kommentare",
|
||||
"ratings": "Bewertungen",
|
||||
"lastSeen": "Zuletzt gesehen"
|
||||
},
|
||||
"view": "",
|
||||
"export": "",
|
||||
"forgetGuest": "",
|
||||
"loadingDetail": "",
|
||||
"view": "Details anzeigen",
|
||||
"export": "Exportieren",
|
||||
"forgetGuest": "Gast entfernen",
|
||||
"loadingDetail": "Auswahl wird geladen...",
|
||||
"detail": {
|
||||
"noComments": "",
|
||||
"empty": ""
|
||||
"noComments": "Keine Kommentare",
|
||||
"empty": "Keine Auswahl in dieser Kategorie",
|
||||
"all": "Alle",
|
||||
"liked": "Gemocht",
|
||||
"favorited": "Favorisiert",
|
||||
"rated": "Bewertet",
|
||||
"commented": "Kommentiert"
|
||||
},
|
||||
"inviteStatus": {
|
||||
"pending": "Ausstehend",
|
||||
"redeemed": "Eingelöst",
|
||||
"revoked": "Widerrufen"
|
||||
}
|
||||
},
|
||||
"analytics": "Analytik",
|
||||
"activities": {
|
||||
"event_created": "Neues Ereignis erstellt: {{eventName}}",
|
||||
"photos_uploaded": "{{count}} Fotos hochgeladen in {{eventName}}",
|
||||
"event_archived": "Ereignis archiviert: {{eventName}}",
|
||||
"archive_restored": "Archiv wiederhergestellt: {{eventName}}",
|
||||
"archive_deleted": "Archiv gelöscht: {{eventName}}",
|
||||
"archive_downloaded": "Archiv heruntergeladen: {{eventName}}",
|
||||
"email_config_updated": "E-Mail-Konfiguration aktualisiert",
|
||||
"email_template_updated": "E-Mail-Vorlage aktualisiert: {{template}}",
|
||||
"branding_updated": "Branding-Einstellungen aktualisiert",
|
||||
"theme_updated": "Theme-Einstellungen aktualisiert",
|
||||
"bulk_download": "{{count}} Fotos heruntergeladen von {{eventName}}",
|
||||
"gallery_password_entry": "Passwort eingegeben für {{eventName}}",
|
||||
"expiration_warning_viewed": "Ablaufwarnung angesehen für {{eventName}}",
|
||||
"event_updated": "Ereignis aktualisiert: {{eventName}}",
|
||||
"event_deleted": "Ereignis gelöscht: {{eventName}}",
|
||||
"event_activated": "Ereignis aktiviert: {{eventName}}",
|
||||
"event_deactivated": "Ereignis deaktiviert: {{eventName}}",
|
||||
"bulk_archive_completed": "Massenarchivierung abgeschlossen",
|
||||
"photo_deleted": "Foto gelöscht von {{eventName}}",
|
||||
"photos_bulk_deleted": "{{count}} Fotos gelöscht von {{eventName}}",
|
||||
"password_changed": "Passwort geändert",
|
||||
"admin_logout": "Admin {{actorName}} abgemeldet",
|
||||
"category_created": "Kategorie erstellt: {{categoryName}}",
|
||||
"category_updated": "Kategorie aktualisiert: {{categoryName}}",
|
||||
"category_deleted": "Kategorie gelöscht: {{categoryName}}",
|
||||
"feedback_settings_updated": "Feedback-Einstellungen aktualisiert",
|
||||
"feedback_moderated": "Feedback moderiert",
|
||||
"feedback_deleted": "Feedback gelöscht",
|
||||
"word_filter_added": "Wortfilter hinzugefügt",
|
||||
"cms_page_updated": "CMS-Seite aktualisiert: {{page}}",
|
||||
"email_resent": "Erstellungs-E-Mail erneut gesendet für: {{eventName}}",
|
||||
"settings_updated": "Einstellungen aktualisiert",
|
||||
"favicon_uploaded": "Favicon hochgeladen",
|
||||
"password_reset": "Passwort zurückgesetzt für: {{eventName}}",
|
||||
"external_import_completed": "Externer Medienimport abgeschlossen ({{imported}} importiert, {{skipped}} übersprungen)",
|
||||
"photo_like": "Foto geliked in {{eventName}}",
|
||||
"photo_favorite": "Foto als Favorit markiert in {{eventName}}",
|
||||
"general_settings_updated": "Allgemeine Einstellungen aktualisiert",
|
||||
"analytics_settings_updated": "Analytik-Einstellungen aktualisiert",
|
||||
"security_settings_updated": "Sicherheitseinstellungen aktualisiert",
|
||||
"event_renamed": "Ereignis umbenannt: {{eventName}}",
|
||||
"photo_rating": "Foto bewertet in {{eventName}}",
|
||||
"photo_comment": "Foto kommentiert in {{eventName}}",
|
||||
"guest_feedback_like": "Gast hat ein Foto geliked in {{eventName}}",
|
||||
"guest_feedback_favorite": "Gast hat ein Foto als Favorit markiert in {{eventName}}",
|
||||
"guest_feedback_rating": "Gast hat ein Foto bewertet in {{eventName}}",
|
||||
"guest_feedback_comment": "Gast hat ein Foto kommentiert in {{eventName}}",
|
||||
"system_activity": "Systemaktivität: {{type}}",
|
||||
"unknown": "Unbekannte Aktivität"
|
||||
}
|
||||
},
|
||||
"acceptInvitation": {
|
||||
@@ -1857,8 +1965,8 @@
|
||||
"requiredFields": "Bitte füllen Sie alle Pflichtfelder aus",
|
||||
"enterTestEmail": "Bitte geben Sie eine Test-E-Mail-Adresse ein",
|
||||
"eventCreationFailed": "Veranstaltung konnte nicht erstellt werden",
|
||||
"noShareLink": "",
|
||||
"copyFailed": ""
|
||||
"noShareLink": "Kein Freigabe-Link verfügbar",
|
||||
"copyFailed": "Link konnte nicht kopiert werden"
|
||||
},
|
||||
"legal": {
|
||||
"impressum": "Impressum",
|
||||
@@ -1880,8 +1988,8 @@
|
||||
"brandingUpdated": "Branding erfolgreich aktualisiert",
|
||||
"categoryUpdated": "Kategorie erfolgreich aktualisiert",
|
||||
"emailConfigSaved": "E-Mail-Konfiguration erfolgreich gespeichert",
|
||||
"brandingThemeMissing": "",
|
||||
"brandingPaletteSynced": ""
|
||||
"brandingThemeMissing": "Es wurde noch kein Branding-Theme gespeichert.",
|
||||
"brandingPaletteSynced": "Palette vom Branding synchronisiert."
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Analytics Dashboard",
|
||||
@@ -1978,22 +2086,22 @@
|
||||
"noTranslation": "Noch keine Übersetzung",
|
||||
"noTranslationYet": "Für diese Sprache existiert noch keine Übersetzung. Kopieren Sie von einer vorhandenen Sprache:",
|
||||
"copyFrom": "Kopieren von",
|
||||
"syncedFromBranding": "",
|
||||
"syncFromBranding": "",
|
||||
"primaryColorHelp": "",
|
||||
"secondaryColorHelp": "",
|
||||
"bodyBgColor": "",
|
||||
"bodyBgColorHelp": "",
|
||||
"containerBgColor": "",
|
||||
"containerBgColorHelp": "",
|
||||
"listBgColor": "",
|
||||
"listBgColorHelp": "",
|
||||
"bodyTextColor": "",
|
||||
"bodyTextColorHelp": "",
|
||||
"mutedTextColor": "",
|
||||
"mutedTextColorHelp": "",
|
||||
"buttonTextColor": "",
|
||||
"buttonTextColorHelp": ""
|
||||
"syncedFromBranding": "E-Mail-Farben vom Branding synchronisiert. Klicken Sie auf Speichern, um sie anzuwenden.",
|
||||
"syncFromBranding": "Vom Branding synchronisieren",
|
||||
"primaryColorHelp": "Kopfzeile, H2-Überschriften, Button-Hintergrund, Linkfarbe. Entspricht Branding → Akzent (gefüllt).",
|
||||
"secondaryColorHelp": "Fußzeilen-Hintergrund. Entspricht Branding → Oberfläche.",
|
||||
"bodyBgColor": "Seitenhintergrund",
|
||||
"bodyBgColorHelp": "Der Rahmen um die E-Mail-Karte — was der Empfänger hinter der E-Mail sieht. Entspricht Branding → Hintergrund.",
|
||||
"containerBgColor": "E-Mail-Karte",
|
||||
"containerBgColorHelp": "Die weiße Karte, die den E-Mail-Inhalt enthält. Entspricht Branding → Oberfläche.",
|
||||
"listBgColor": "Infopanel",
|
||||
"listBgColorHelp": "Hintergrund der Aufzählungs-Infopanels im E-Mail-Text. Entspricht Branding → Erhöht.",
|
||||
"bodyTextColor": "Fließtext",
|
||||
"bodyTextColorHelp": "Absatz- und Fetttext-Farbe. Entspricht Branding → Primärer Text.",
|
||||
"mutedTextColor": "Fußzeilentext",
|
||||
"mutedTextColorHelp": "Fußzeilentext und Copyright-Zeile. Entspricht Branding → Sekundärer Text.",
|
||||
"buttonTextColor": "Button-Text",
|
||||
"buttonTextColorHelp": "Textfarbe auf gefüllten Buttons. Sollte klar gegen die Primärfarbe kontrastieren. Kein Branding-Äquivalent — meistens weiß."
|
||||
},
|
||||
"cms": {
|
||||
"title": "CMS-Seiten",
|
||||
@@ -2015,14 +2123,14 @@
|
||||
"externalUrlPlaceholder": "https://example.com/impressum",
|
||||
"externalUrlInvalid": "Muss eine gültige https://-URL sein",
|
||||
"externalUrlActive": "Externe URL ist aktiv — interne Inhalte bleiben gespeichert, werden Besuchern aber nicht angezeigt.",
|
||||
"logoUploaded": "",
|
||||
"logoCleared": "",
|
||||
"pageLogo": "",
|
||||
"pageLogoHelp": "",
|
||||
"noLogo": "",
|
||||
"replaceLogo": "",
|
||||
"uploadLogo": "",
|
||||
"clearLogo": ""
|
||||
"logoUploaded": "Logo hochgeladen",
|
||||
"logoCleared": "Logo entfernt",
|
||||
"pageLogo": "Seitenlogo",
|
||||
"pageLogoHelp": "Optional. Falls gesetzt, wird es anstelle des globalen Branding-Logos auf dieser Seite verwendet.",
|
||||
"noLogo": "keine Überschreibung",
|
||||
"replaceLogo": "Logo ersetzen",
|
||||
"uploadLogo": "Logo hochladen",
|
||||
"clearLogo": "Seitenstandard verwenden"
|
||||
},
|
||||
"validation": {
|
||||
"eventNameRequired": "Veranstaltungsname ist erforderlich",
|
||||
@@ -2034,10 +2142,10 @@
|
||||
"passwordMinLength": "Passwort muss mindestens 6 Zeichen lang sein",
|
||||
"passwordsDoNotMatch": "Passwörter stimmen nicht überein",
|
||||
"expirationRange": "Ablauf muss zwischen 1 und 365 Tagen liegen",
|
||||
"required": "",
|
||||
"expirationRequired": "",
|
||||
"eventDateRequired": "",
|
||||
"passwordTooSimple": ""
|
||||
"required": "Dieses Feld ist erforderlich",
|
||||
"expirationRequired": "Ablaufdatum ist erforderlich.",
|
||||
"eventDateRequired": "Veranstaltungsdatum ist erforderlich",
|
||||
"passwordTooSimple": "Das Passwort darf nicht nur aus Zahlen bestehen. Verwenden Sie z.B. ein Datumsformat wie \"04.07.2025\""
|
||||
},
|
||||
"maintenance": {
|
||||
"title": "Systemwartung",
|
||||
@@ -2133,96 +2241,96 @@
|
||||
"commentsDesc": "Textkommentare auf Fotos",
|
||||
"favorites": "Favoriten",
|
||||
"favoritesDesc": "Fotos als Favoriten markieren",
|
||||
"identityMode": "",
|
||||
"identityModeSimple": "",
|
||||
"identityModeSimpleDesc": "",
|
||||
"identityModeGuest": "",
|
||||
"identityModeGuestDesc": "",
|
||||
"privacyModeration": "",
|
||||
"requireInfo": "",
|
||||
"requireInfoDesc": "",
|
||||
"moderateComments": "",
|
||||
"moderateCommentsDesc": "",
|
||||
"showToGuests": "",
|
||||
"showToGuestsDesc": "",
|
||||
"enableRateLimiting": "",
|
||||
"rateLimitingDesc": "",
|
||||
"timeWindow": "",
|
||||
"maxRequests": ""
|
||||
"identityMode": "Identitätsmodus",
|
||||
"identityModeSimple": "Einfaches Feedback",
|
||||
"identityModeSimpleDesc": "Anonym, gerätebasiert. Alle Besucher auf demselben Gerät teilen den Zustand.",
|
||||
"identityModeGuest": "Gastbezogene Auswahl",
|
||||
"identityModeGuestDesc": "Jeder Besucher gibt seinen Namen ein. Ermöglicht gastbezogenes Tracking und Admin-Einblicke.",
|
||||
"privacyModeration": "Datenschutz & Moderation",
|
||||
"requireInfo": "Name & E-Mail erforderlich",
|
||||
"requireInfoDesc": "Gäste müssen Name und E-Mail angeben, um Feedback zu hinterlassen",
|
||||
"moderateComments": "Kommentare moderieren",
|
||||
"moderateCommentsDesc": "Kommentare benötigen eine Genehmigung, bevor sie sichtbar sind",
|
||||
"showToGuests": "Feedback für Gäste anzeigen",
|
||||
"showToGuestsDesc": "Andere Gäste können Bewertungen, Likes und genehmigte Kommentare sehen",
|
||||
"enableRateLimiting": "Ratenbegrenzung aktivieren",
|
||||
"rateLimitingDesc": "Spam durch Begrenzung der Feedback-Häufigkeit verhindern",
|
||||
"timeWindow": "Zeitfenster (Minuten)",
|
||||
"maxRequests": "Maximale Anfragen"
|
||||
},
|
||||
"settingsUpdated": "",
|
||||
"settingsUpdateError": "",
|
||||
"moderated": "",
|
||||
"deleted": "",
|
||||
"exported": "",
|
||||
"exportError": "",
|
||||
"title": "",
|
||||
"exportCSV": "",
|
||||
"exportJSON": "",
|
||||
"settingsUpdated": "Feedback-Einstellungen aktualisiert",
|
||||
"settingsUpdateError": "Einstellungen konnten nicht aktualisiert werden",
|
||||
"moderated": "Feedback moderiert",
|
||||
"deleted": "Feedback gelöscht",
|
||||
"exported": "Feedback exportiert",
|
||||
"exportError": "Feedback konnte nicht exportiert werden",
|
||||
"title": "Feedback-Verwaltung",
|
||||
"exportCSV": "CSV exportieren",
|
||||
"exportJSON": "JSON exportieren",
|
||||
"tabs": {
|
||||
"settings": "",
|
||||
"feedback": "",
|
||||
"analytics": "",
|
||||
"moderation": ""
|
||||
"settings": "Einstellungen",
|
||||
"feedback": "Feedback",
|
||||
"analytics": "Analytik",
|
||||
"moderation": "Moderation"
|
||||
},
|
||||
"allTypes": "",
|
||||
"allTypes": "Alle Typen",
|
||||
"types": {
|
||||
"rating": "",
|
||||
"like": "",
|
||||
"comment": "",
|
||||
"favorite": ""
|
||||
"rating": "Bewertungen",
|
||||
"like": "Likes",
|
||||
"comment": "Kommentare",
|
||||
"favorite": "Favoriten"
|
||||
},
|
||||
"allStatuses": "",
|
||||
"allStatuses": "Alle Status",
|
||||
"status": {
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"hidden": ""
|
||||
"pending": "Ausstehend",
|
||||
"approved": "Genehmigt",
|
||||
"hidden": "Versteckt"
|
||||
},
|
||||
"noFeedback": "",
|
||||
"approve": "",
|
||||
"hide": "",
|
||||
"unhide": "",
|
||||
"confirmDelete": "",
|
||||
"avgRating": "",
|
||||
"totalRatings_one": "",
|
||||
"totalRatings_other": "",
|
||||
"totalLikes": "",
|
||||
"totalComments": "",
|
||||
"pendingModeration_one": "",
|
||||
"pendingModeration_other": "",
|
||||
"totalInteractions": "",
|
||||
"topRated": "",
|
||||
"recentComments": "",
|
||||
"wordFilters": "",
|
||||
"wordFiltersDesc": "",
|
||||
"manageFilters": "",
|
||||
"manage": "",
|
||||
"ratingSubmitted": "",
|
||||
"ratingError": "",
|
||||
"rateStar_one": "",
|
||||
"rateStar_other": "",
|
||||
"ratingsCount_one": "",
|
||||
"ratingsCount_other": "",
|
||||
"likeError": "",
|
||||
"unlike": "",
|
||||
"like": "",
|
||||
"favoriteError": "",
|
||||
"unfavorite": "",
|
||||
"favorite": "",
|
||||
"invalidEmail": "",
|
||||
"identityRequired": "",
|
||||
"identityReason": "",
|
||||
"namePlaceholder": "",
|
||||
"emailPlaceholder": "",
|
||||
"submitFeedback": "",
|
||||
"moderationSuccess": "",
|
||||
"pendingModeration": "",
|
||||
"pending": "",
|
||||
"noPendingComments": "",
|
||||
"onPhoto": "",
|
||||
"showAll_one": "",
|
||||
"showAll_other": "",
|
||||
"viewAllFeedback": ""
|
||||
"noFeedback": "Kein Feedback gefunden",
|
||||
"approve": "Genehmigen",
|
||||
"hide": "Ausblenden",
|
||||
"unhide": "Einblenden",
|
||||
"confirmDelete": "Dieses Feedback wirklich löschen?",
|
||||
"avgRating": "Durchschnittsbewertung",
|
||||
"totalRatings_one": "{{count}} Bewertungen",
|
||||
"totalRatings_other": "{{count}} Bewertungen",
|
||||
"totalLikes": "Gesamte Likes",
|
||||
"totalComments": "Gesamte Kommentare",
|
||||
"pendingModeration_one": "{{count}} ausstehend",
|
||||
"pendingModeration_other": "{{count}} ausstehend",
|
||||
"totalInteractions": "Gesamte Interaktionen",
|
||||
"topRated": "Bestbewertete Fotos",
|
||||
"recentComments": "Neueste Kommentare",
|
||||
"wordFilters": "Wortfilter",
|
||||
"wordFiltersDesc": "Gesperrte Wörter für die Kommentarmoderation verwalten",
|
||||
"manageFilters": "Wortfilter verwalten",
|
||||
"manage": "Feedback verwalten",
|
||||
"ratingSubmitted": "Bewertung abgegeben",
|
||||
"ratingError": "Bewertung konnte nicht abgegeben werden",
|
||||
"rateStar_one": "{{count}} Stern bewerten",
|
||||
"rateStar_other": "{{count}} Sterne bewerten",
|
||||
"ratingsCount_one": "{{count}} Bewertungen",
|
||||
"ratingsCount_other": "{{count}} Bewertungen",
|
||||
"likeError": "Like konnte nicht aktualisiert werden",
|
||||
"unlike": "Nicht mehr mögen",
|
||||
"like": "Gefällt mir",
|
||||
"favoriteError": "Favorit konnte nicht aktualisiert werden",
|
||||
"unfavorite": "Aus Favoriten entfernen",
|
||||
"favorite": "Zu Favoriten hinzufügen",
|
||||
"invalidEmail": "Ungültige E-Mail-Adresse",
|
||||
"identityRequired": "Ihre Informationen erforderlich",
|
||||
"identityReason": "Bitte geben Sie Ihren Namen und Ihre E-Mail an, um {{type}} zu senden.",
|
||||
"namePlaceholder": "Ihren Namen eingeben",
|
||||
"emailPlaceholder": "Ihre E-Mail eingeben",
|
||||
"submitFeedback": "Feedback senden",
|
||||
"moderationSuccess": "Feedback moderiert",
|
||||
"pendingModeration": "Ausstehende Moderation",
|
||||
"pending": "ausstehend",
|
||||
"noPendingComments": "Keine Kommentare warten auf Moderation",
|
||||
"onPhoto": "Auf Foto",
|
||||
"showAll_one": "Alle {{count}} ausstehenden Kommentare anzeigen",
|
||||
"showAll_other": "Alle {{count}} ausstehenden Kommentare anzeigen",
|
||||
"viewAllFeedback": "Gesamtes Feedback & Einstellungen anzeigen"
|
||||
},
|
||||
"filter": {
|
||||
"feedbackFilters": "Feedback-Filter",
|
||||
@@ -2233,7 +2341,14 @@
|
||||
"hasComments": "Hat Kommentare",
|
||||
"showingPhotos": "Fotos gesamt",
|
||||
"withRatings": "Mit Bewertungen",
|
||||
"combineWith": ""
|
||||
"combineWith": "Kombinieren mit",
|
||||
"allPhotos": "Alle Fotos",
|
||||
"anyRating": "Jede Bewertung",
|
||||
"oneStarPlus": "1+ Stern",
|
||||
"twoStarsPlus": "2+ Sterne",
|
||||
"threeStarsPlus": "3+ Sterne",
|
||||
"fourStarsPlus": "4+ Sterne",
|
||||
"fiveStarsOnly": "Nur 5 Sterne"
|
||||
},
|
||||
"export": {
|
||||
"button": "Exportieren",
|
||||
@@ -2241,8 +2356,8 @@
|
||||
"error": "Export fehlgeschlagen: ",
|
||||
"exportFiltered": "Gefilterte Fotos exportieren",
|
||||
"hint": "Fotos auswählen oder Filter anwenden zum Exportieren",
|
||||
"exportSelected_one": "",
|
||||
"exportSelected_other": ""
|
||||
"exportSelected_one": "{{count}} ausgewähltes exportieren",
|
||||
"exportSelected_other": "{{count}} ausgewählte exportieren"
|
||||
},
|
||||
"clientAccess": {
|
||||
"title": "Kundenzugang",
|
||||
@@ -2324,15 +2439,15 @@
|
||||
"dateTaken": "Aufnahmedatum"
|
||||
},
|
||||
"photos": {
|
||||
"moveToCategory_one": "",
|
||||
"moveToCategory_other": "",
|
||||
"selectCategory": "",
|
||||
"uncategorized": "",
|
||||
"movePhotos": "",
|
||||
"selectedCategory": "",
|
||||
"movedToCategory_one": "",
|
||||
"movedToCategory_other": "",
|
||||
"moveToCategoryFailed": "",
|
||||
"moveToCategory": ""
|
||||
"moveToCategory_one": "{{count}} Foto in Kategorie verschieben",
|
||||
"moveToCategory_other": "{{count}} Fotos in Kategorie verschieben",
|
||||
"selectCategory": "Kategorie auswählen",
|
||||
"uncategorized": "Ohne Kategorie",
|
||||
"movePhotos": "Fotos verschieben",
|
||||
"selectedCategory": "Ausgewählte Kategorie",
|
||||
"movedToCategory_one": "{{count}} Foto nach {{category}} verschoben",
|
||||
"movedToCategory_other": "{{count}} Fotos nach {{category}} verschoben",
|
||||
"moveToCategoryFailed": "Fotos konnten nicht in die Kategorie verschoben werden",
|
||||
"moveToCategory": "In Kategorie verschieben"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,14 @@
|
||||
"navigation": {
|
||||
"dashboard": "Dashboard",
|
||||
"events": "Events",
|
||||
"settings": "Settings"
|
||||
"archives": "Archives",
|
||||
"settings": "Settings",
|
||||
"eventTypes": "Event Types",
|
||||
"branding": "Branding",
|
||||
"emailSettings": "Email Settings",
|
||||
"backup": "Backup & Restore",
|
||||
"cmsPages": "CMS Pages",
|
||||
"users": "Users"
|
||||
},
|
||||
"archives": {
|
||||
"title": "Archives",
|
||||
@@ -1295,7 +1302,37 @@
|
||||
"noTemplate": "No Template",
|
||||
"noTemplateDescription": "Use only theme settings without a CSS template",
|
||||
"templateSlot": "Slot {{slot}}",
|
||||
"eventCustomCSS": "Event-specific Custom CSS"
|
||||
"eventCustomCSS": "Event-specific Custom CSS",
|
||||
"layoutDescriptions": {
|
||||
"grid": "Classic grid layout with consistent photo sizes",
|
||||
"masonry": "Pinterest-style layout with varied heights",
|
||||
"carousel": "Full-screen slideshow with navigation",
|
||||
"timeline": "Photos organized by date",
|
||||
"mosaic": "Artistic layout with mixed sizes",
|
||||
"gallery-premium": "Elegant light theme with hero and masonry (Beta)",
|
||||
"gallery-story": "Cinematic dark theme with scene sections (Beta)"
|
||||
},
|
||||
"headerStyleOptions": {
|
||||
"hero": "Hero Image",
|
||||
"standard": "Standard",
|
||||
"banner": "Banner",
|
||||
"minimal": "Minimal",
|
||||
"none": "No Header"
|
||||
},
|
||||
"headerStyleDescriptions": {
|
||||
"hero": "Full-height image with event info overlay",
|
||||
"standard": "Compact inline header with event details",
|
||||
"banner": "Standard header plus a colored banner above",
|
||||
"minimal": "Compact header with essential info",
|
||||
"none": "Hide header completely"
|
||||
},
|
||||
"dividerOptions": {
|
||||
"wave": "Wave",
|
||||
"straight": "Straight",
|
||||
"angle": "Angle",
|
||||
"curve": "Curve",
|
||||
"none": "None"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "Admin Panel",
|
||||
@@ -1309,6 +1346,9 @@
|
||||
"archivedEvents": "Archived Events",
|
||||
"systemHealth": "System Health",
|
||||
"health": {
|
||||
"healthy": "Healthy",
|
||||
"warning": "Warning",
|
||||
"error": "Error",
|
||||
"checking": "Checking..."
|
||||
},
|
||||
"updates": {
|
||||
@@ -1465,8 +1505,70 @@
|
||||
"loadingDetail": "Loading selections...",
|
||||
"detail": {
|
||||
"noComments": "No comments",
|
||||
"empty": "No selections in this category"
|
||||
"empty": "No selections in this category",
|
||||
"all": "All",
|
||||
"liked": "Liked",
|
||||
"favorited": "Favorited",
|
||||
"rated": "Rated",
|
||||
"commented": "Commented"
|
||||
},
|
||||
"inviteStatus": {
|
||||
"pending": "Pending",
|
||||
"redeemed": "Redeemed",
|
||||
"revoked": "Revoked"
|
||||
}
|
||||
},
|
||||
"analytics": "Analytics",
|
||||
"activities": {
|
||||
"event_created": "New event created: {{eventName}}",
|
||||
"photos_uploaded": "{{count}} photos uploaded to {{eventName}}",
|
||||
"event_archived": "Event archived: {{eventName}}",
|
||||
"archive_restored": "Archive restored: {{eventName}}",
|
||||
"archive_deleted": "Archive deleted: {{eventName}}",
|
||||
"archive_downloaded": "Archive downloaded: {{eventName}}",
|
||||
"email_config_updated": "Email configuration updated",
|
||||
"email_template_updated": "Email template updated: {{template}}",
|
||||
"branding_updated": "Branding settings updated",
|
||||
"theme_updated": "Theme settings updated",
|
||||
"bulk_download": "{{count}} photos downloaded from {{eventName}}",
|
||||
"gallery_password_entry": "Password entered for {{eventName}}",
|
||||
"expiration_warning_viewed": "Expiration warning viewed for {{eventName}}",
|
||||
"feedback_settings_updated": "Feedback settings updated",
|
||||
"feedback_moderated": "Feedback moderated",
|
||||
"feedback_deleted": "Feedback deleted",
|
||||
"photo_like": "Photo liked in {{eventName}}",
|
||||
"photo_favorite": "Photo favorited in {{eventName}}",
|
||||
"photo_rating": "Photo rated in {{eventName}}",
|
||||
"photo_comment": "Photo commented in {{eventName}}",
|
||||
"guest_feedback_like": "Guest liked a photo in {{eventName}}",
|
||||
"guest_feedback_favorite": "Guest favorited a photo in {{eventName}}",
|
||||
"guest_feedback_rating": "Guest rated a photo in {{eventName}}",
|
||||
"guest_feedback_comment": "Guest commented on a photo in {{eventName}}",
|
||||
"word_filter_added": "Word filter added",
|
||||
"external_import_completed": "External media import completed ({{imported}} imported, {{skipped}} skipped)",
|
||||
"bulk_archive_completed": "Bulk archive completed",
|
||||
"event_activated": "Event activated: {{eventName}}",
|
||||
"event_deactivated": "Event deactivated: {{eventName}}",
|
||||
"photo_deleted": "Photo deleted from {{eventName}}",
|
||||
"photos_bulk_deleted": "{{count}} photos deleted from {{eventName}}",
|
||||
"settings_updated": "Settings updated",
|
||||
"event_updated": "Event updated: {{eventName}}",
|
||||
"event_renamed": "Event renamed: {{eventName}}",
|
||||
"event_deleted": "Event deleted: {{eventName}}",
|
||||
"password_changed": "Password changed",
|
||||
"email_resent": "Creation email resent for: {{eventName}}",
|
||||
"category_created": "Category created: {{categoryName}}",
|
||||
"category_updated": "Category updated: {{categoryName}}",
|
||||
"category_deleted": "Category deleted: {{categoryName}}",
|
||||
"general_settings_updated": "General settings updated",
|
||||
"favicon_uploaded": "Favicon uploaded",
|
||||
"analytics_settings_updated": "Analytics settings updated",
|
||||
"cms_page_updated": "CMS page updated: {{page}}",
|
||||
"security_settings_updated": "Security settings updated",
|
||||
"password_reset": "Password reset for: {{eventName}}",
|
||||
"admin_logout": "Admin {{actorName}} logged out",
|
||||
"system_activity": "System activity: {{type}}",
|
||||
"unknown": "Unknown activity"
|
||||
}
|
||||
},
|
||||
"acceptInvitation": {
|
||||
@@ -1799,7 +1901,13 @@
|
||||
},
|
||||
"storageDestination": "Storage Destination",
|
||||
"backupType": "{{type}} backup",
|
||||
"noDestinationSet": "No destination set"
|
||||
"noDestinationSet": "No destination set",
|
||||
"healthStatus": {
|
||||
"excellent": "Excellent",
|
||||
"good": "Good",
|
||||
"warning": "Warning",
|
||||
"critical": "Critical"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"enableBackup": "Enable Automated Backups",
|
||||
@@ -2254,7 +2362,14 @@
|
||||
"hasComments": "Has comments",
|
||||
"showingPhotos": "Total photos",
|
||||
"withRatings": "With ratings",
|
||||
"combineWith": "Combine with"
|
||||
"combineWith": "Combine with",
|
||||
"allPhotos": "All Photos",
|
||||
"anyRating": "Any Rating",
|
||||
"oneStarPlus": "1+ Stars",
|
||||
"twoStarsPlus": "2+ Stars",
|
||||
"threeStarsPlus": "3+ Stars",
|
||||
"fourStarsPlus": "4+ Stars",
|
||||
"fiveStarsOnly": "5 Stars Only"
|
||||
},
|
||||
"adminLogin": {
|
||||
"title": "Admin Login",
|
||||
|
||||
@@ -161,7 +161,14 @@
|
||||
"navigation": {
|
||||
"dashboard": "Tableau de bord",
|
||||
"events": "Événements",
|
||||
"settings": "Paramètres"
|
||||
"settings": "Paramètres",
|
||||
"archives": "Archives",
|
||||
"emailSettings": "Paramètres e-mail",
|
||||
"branding": "Identité visuelle",
|
||||
"eventTypes": "Types d'événements",
|
||||
"backup": "Sauvegarde et Restauration",
|
||||
"cmsPages": "Pages CMS",
|
||||
"users": "Utilisateurs"
|
||||
},
|
||||
"archives": {
|
||||
"title": "Archives",
|
||||
@@ -1307,7 +1314,37 @@
|
||||
"noTemplate": "Aucun modèle",
|
||||
"noTemplateDescription": "Utiliser uniquement les paramètres de thème sans modèle CSS",
|
||||
"templateSlot": "Emplacement {{slot}}",
|
||||
"eventCustomCSS": "CSS personnalisé par événement"
|
||||
"eventCustomCSS": "CSS personnalisé par événement",
|
||||
"layoutDescriptions": {
|
||||
"grid": "Disposition en grille classique avec tailles de photos uniformes",
|
||||
"masonry": "Disposition de type Pinterest avec hauteurs variées",
|
||||
"carousel": "Diaporama plein écran avec navigation",
|
||||
"timeline": "Photos organisées par date",
|
||||
"mosaic": "Disposition artistique avec tailles mixtes",
|
||||
"gallery-premium": "Thème clair élégant avec hero et masonry (Bêta)",
|
||||
"gallery-story": "Thème sombre cinématographique avec sections de scènes (Bêta)"
|
||||
},
|
||||
"headerStyleOptions": {
|
||||
"hero": "Image Hero",
|
||||
"standard": "Standard",
|
||||
"banner": "Bannière",
|
||||
"minimal": "Minimal",
|
||||
"none": "Pas d'en-tête"
|
||||
},
|
||||
"headerStyleDescriptions": {
|
||||
"hero": "Image pleine hauteur avec superposition d'informations sur l'événement",
|
||||
"standard": "En-tête compact avec détails de l'événement",
|
||||
"banner": "En-tête standard avec bannière colorée au-dessus",
|
||||
"minimal": "En-tête compact avec informations essentielles",
|
||||
"none": "Masquer complètement l'en-tête"
|
||||
},
|
||||
"dividerOptions": {
|
||||
"none": "Aucun",
|
||||
"wave": "Vague",
|
||||
"straight": "Droit",
|
||||
"angle": "Angle",
|
||||
"curve": "Courbe"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "Panneau d'administration",
|
||||
@@ -1321,7 +1358,10 @@
|
||||
"archivedEvents": "Événements archivés",
|
||||
"systemHealth": "Santé système",
|
||||
"health": {
|
||||
"checking": "Vérification..."
|
||||
"checking": "Vérification...",
|
||||
"healthy": "Sain",
|
||||
"warning": "Avertissement",
|
||||
"error": "Erreur"
|
||||
},
|
||||
"updates": {
|
||||
"available": "Mise à jour disponible",
|
||||
@@ -1475,7 +1515,17 @@
|
||||
"loadingDetail": "Chargement des sélections…",
|
||||
"detail": {
|
||||
"noComments": "Aucun commentaire",
|
||||
"empty": "Aucune sélection dans cette catégorie"
|
||||
"empty": "Aucune sélection dans cette catégorie",
|
||||
"all": "Tout",
|
||||
"liked": "Aimé",
|
||||
"favorited": "Favori",
|
||||
"rated": "Noté",
|
||||
"commented": "Commenté"
|
||||
},
|
||||
"inviteStatus": {
|
||||
"pending": "En attente",
|
||||
"redeemed": "Utilisé",
|
||||
"revoked": "Révoqué"
|
||||
}
|
||||
},
|
||||
"photos": {
|
||||
@@ -1487,6 +1537,58 @@
|
||||
"processingStatus": "En cours…",
|
||||
"processingFailed": "Échoué",
|
||||
"retryQueued": "Nouvelle tentative en file d'attente"
|
||||
},
|
||||
"analytics": "Analytique",
|
||||
"activities": {
|
||||
"event_created": "Nouvel événement créé : {{eventName}}",
|
||||
"photos_uploaded": "{{count}} photos téléversées vers {{eventName}}",
|
||||
"event_archived": "Événement archivé : {{eventName}}",
|
||||
"archive_restored": "Archive restaurée : {{eventName}}",
|
||||
"archive_deleted": "Archive supprimée : {{eventName}}",
|
||||
"archive_downloaded": "Archive téléchargée : {{eventName}}",
|
||||
"email_config_updated": "Configuration e-mail mise à jour",
|
||||
"email_template_updated": "Modèle e-mail mis à jour : {{template}}",
|
||||
"branding_updated": "Paramètres d'identité visuelle mis à jour",
|
||||
"theme_updated": "Paramètres de thème mis à jour",
|
||||
"bulk_download": "{{count}} photos téléchargées depuis {{eventName}}",
|
||||
"gallery_password_entry": "Mot de passe saisi pour {{eventName}}",
|
||||
"expiration_warning_viewed": "Avertissement d'expiration consulté pour {{eventName}}",
|
||||
"event_updated": "Événement mis à jour : {{eventName}}",
|
||||
"event_deleted": "Événement supprimé : {{eventName}}",
|
||||
"event_activated": "Événement activé : {{eventName}}",
|
||||
"event_deactivated": "Événement désactivé : {{eventName}}",
|
||||
"bulk_archive_completed": "Archivage en masse terminé",
|
||||
"photo_deleted": "Photo supprimée de {{eventName}}",
|
||||
"photos_bulk_deleted": "{{count}} photos supprimées de {{eventName}}",
|
||||
"password_changed": "Mot de passe modifié",
|
||||
"admin_logout": "Admin {{actorName}} déconnecté",
|
||||
"category_created": "Catégorie créée : {{categoryName}}",
|
||||
"category_updated": "Catégorie mise à jour : {{categoryName}}",
|
||||
"category_deleted": "Catégorie supprimée : {{categoryName}}",
|
||||
"feedback_settings_updated": "Paramètres d'avis mis à jour",
|
||||
"feedback_moderated": "Avis modéré",
|
||||
"feedback_deleted": "Avis supprimé",
|
||||
"word_filter_added": "Filtre de mots ajouté",
|
||||
"cms_page_updated": "Page CMS mise à jour : {{page}}",
|
||||
"email_resent": "E-mail de création renvoyé pour : {{eventName}}",
|
||||
"settings_updated": "Paramètres mis à jour",
|
||||
"favicon_uploaded": "Favicon téléversé",
|
||||
"password_reset": "Mot de passe réinitialisé pour : {{eventName}}",
|
||||
"external_import_completed": "Importation de médias externes terminée ({{imported}} importés, {{skipped}} ignorés)",
|
||||
"photo_like": "Photo aimée dans {{eventName}}",
|
||||
"photo_favorite": "Photo ajoutée aux favoris dans {{eventName}}",
|
||||
"general_settings_updated": "Paramètres généraux mis à jour",
|
||||
"analytics_settings_updated": "Paramètres d'analytique mis à jour",
|
||||
"security_settings_updated": "Paramètres de sécurité mis à jour",
|
||||
"event_renamed": "Événement renommé : {{eventName}}",
|
||||
"photo_rating": "Photo notée dans {{eventName}}",
|
||||
"photo_comment": "Photo commentée dans {{eventName}}",
|
||||
"guest_feedback_like": "Un invité a aimé une photo dans {{eventName}}",
|
||||
"guest_feedback_favorite": "Un invité a mis en favori une photo dans {{eventName}}",
|
||||
"guest_feedback_rating": "Un invité a noté une photo dans {{eventName}}",
|
||||
"guest_feedback_comment": "Un invité a commenté une photo dans {{eventName}}",
|
||||
"system_activity": "Activité système : {{type}}",
|
||||
"unknown": "Activité inconnue"
|
||||
}
|
||||
},
|
||||
"acceptInvitation": {
|
||||
@@ -1819,7 +1921,13 @@
|
||||
},
|
||||
"storageDestination": "Destination",
|
||||
"backupType": "Sauvegarde {{type}}",
|
||||
"noDestinationSet": "Pas de destination"
|
||||
"noDestinationSet": "Pas de destination",
|
||||
"healthStatus": {
|
||||
"warning": "Avertissement",
|
||||
"excellent": "Excellent",
|
||||
"good": "Bon",
|
||||
"critical": "Critique"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"enableBackup": "Activer les sauvegardes auto",
|
||||
@@ -2279,7 +2387,14 @@
|
||||
"hasComments": "Avec Commentaires",
|
||||
"combineWith": "Combiner avec",
|
||||
"showingPhotos": "Total photos",
|
||||
"withRatings": "Avec notes"
|
||||
"withRatings": "Avec notes",
|
||||
"allPhotos": "Toutes les photos",
|
||||
"anyRating": "Toute note",
|
||||
"oneStarPlus": "1+ étoile",
|
||||
"twoStarsPlus": "2+ étoiles",
|
||||
"threeStarsPlus": "3+ étoiles",
|
||||
"fourStarsPlus": "4+ étoiles",
|
||||
"fiveStarsOnly": "5 étoiles uniquement"
|
||||
},
|
||||
"adminLogin": {
|
||||
"title": "Connexion Admin",
|
||||
|
||||
@@ -158,7 +158,14 @@
|
||||
"navigation": {
|
||||
"dashboard": "Dashboard",
|
||||
"events": "Evenementen",
|
||||
"settings": "Instellingen"
|
||||
"settings": "Instellingen",
|
||||
"archives": "Archieven",
|
||||
"emailSettings": "E-mailinstellingen",
|
||||
"branding": "Huisstijl",
|
||||
"eventTypes": "Evenementtypes",
|
||||
"backup": "Back-up en herstel",
|
||||
"cmsPages": "CMS-pagina's",
|
||||
"users": "Gebruikers"
|
||||
},
|
||||
"archives": {
|
||||
"title": "Archieven",
|
||||
@@ -1295,7 +1302,37 @@
|
||||
"noTemplate": "Geen sjabloon",
|
||||
"noTemplateDescription": "Alleen thema-instellingen gebruiken zonder CSS-sjabloon",
|
||||
"templateSlot": "Slot {{slot}}",
|
||||
"eventCustomCSS": "Evenement-specifieke aangepaste CSS"
|
||||
"eventCustomCSS": "Evenement-specifieke aangepaste CSS",
|
||||
"layoutDescriptions": {
|
||||
"grid": "Klassieke rasterindeling met uniforme fotogroottes",
|
||||
"masonry": "Pinterest-stijl indeling met variabele hoogtes",
|
||||
"carousel": "Volledig scherm diashow met navigatie",
|
||||
"timeline": "Foto's gerangschikt op datum",
|
||||
"mosaic": "Artistieke indeling met gemengde groottes",
|
||||
"gallery-premium": "Elegant licht thema met hero en masonry (Beta)",
|
||||
"gallery-story": "Cinematisch donker thema met scènesecties (Beta)"
|
||||
},
|
||||
"headerStyleOptions": {
|
||||
"hero": "Hero-afbeelding",
|
||||
"standard": "Standaard",
|
||||
"banner": "Banner",
|
||||
"minimal": "Minimaal",
|
||||
"none": "Geen koptekst"
|
||||
},
|
||||
"headerStyleDescriptions": {
|
||||
"hero": "Volledige hoogte afbeelding met evenementinfo overlay",
|
||||
"standard": "Compacte inline koptekst met evenementdetails",
|
||||
"banner": "Standaard koptekst plus een gekleurde banner erboven",
|
||||
"minimal": "Compacte koptekst met essentiële info",
|
||||
"none": "Koptekst volledig verbergen"
|
||||
},
|
||||
"dividerOptions": {
|
||||
"none": "Geen",
|
||||
"wave": "Golf",
|
||||
"straight": "Recht",
|
||||
"angle": "Hoek",
|
||||
"curve": "Boog"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "Beheerpaneel",
|
||||
@@ -1309,7 +1346,10 @@
|
||||
"archivedEvents": "Gearchiveerde evenementen",
|
||||
"systemHealth": "Systeemgezondheid",
|
||||
"health": {
|
||||
"checking": "Controleren..."
|
||||
"checking": "Controleren...",
|
||||
"healthy": "Gezond",
|
||||
"warning": "Waarschuwing",
|
||||
"error": "Fout"
|
||||
},
|
||||
"updates": {
|
||||
"available": "Update beschikbaar",
|
||||
@@ -1465,8 +1505,70 @@
|
||||
"loadingDetail": "Selecties laden…",
|
||||
"detail": {
|
||||
"noComments": "Geen opmerkingen",
|
||||
"empty": "Geen selecties in deze categorie"
|
||||
"empty": "Geen selecties in deze categorie",
|
||||
"all": "Alle",
|
||||
"liked": "Geliked",
|
||||
"favorited": "Favoriet",
|
||||
"rated": "Beoordeeld",
|
||||
"commented": "Becommentarieerd"
|
||||
},
|
||||
"inviteStatus": {
|
||||
"pending": "In behandeling",
|
||||
"redeemed": "Ingewisseld",
|
||||
"revoked": "Ingetrokken"
|
||||
}
|
||||
},
|
||||
"analytics": "Analyse",
|
||||
"activities": {
|
||||
"event_created": "Nieuw evenement aangemaakt: {{eventName}}",
|
||||
"photos_uploaded": "{{count}} foto's geüpload naar {{eventName}}",
|
||||
"event_archived": "Evenement gearchiveerd: {{eventName}}",
|
||||
"archive_restored": "Archief hersteld: {{eventName}}",
|
||||
"archive_deleted": "Archief verwijderd: {{eventName}}",
|
||||
"archive_downloaded": "Archief gedownload: {{eventName}}",
|
||||
"email_config_updated": "E-mailconfiguratie bijgewerkt",
|
||||
"email_template_updated": "E-mailsjabloon bijgewerkt: {{template}}",
|
||||
"branding_updated": "Huisstijlinstellingen bijgewerkt",
|
||||
"theme_updated": "Thema-instellingen bijgewerkt",
|
||||
"bulk_download": "{{count}} foto's gedownload van {{eventName}}",
|
||||
"gallery_password_entry": "Wachtwoord ingevoerd voor {{eventName}}",
|
||||
"expiration_warning_viewed": "Verloopwaarschuwing bekeken voor {{eventName}}",
|
||||
"event_updated": "Evenement bijgewerkt: {{eventName}}",
|
||||
"event_deleted": "Evenement verwijderd: {{eventName}}",
|
||||
"event_activated": "Evenement geactiveerd: {{eventName}}",
|
||||
"event_deactivated": "Evenement gedeactiveerd: {{eventName}}",
|
||||
"bulk_archive_completed": "Bulkarchivering voltooid",
|
||||
"photo_deleted": "Foto verwijderd van {{eventName}}",
|
||||
"photos_bulk_deleted": "{{count}} foto's verwijderd van {{eventName}}",
|
||||
"password_changed": "Wachtwoord gewijzigd",
|
||||
"admin_logout": "Beheerder {{actorName}} uitgelogd",
|
||||
"category_created": "Categorie aangemaakt: {{categoryName}}",
|
||||
"category_updated": "Categorie bijgewerkt: {{categoryName}}",
|
||||
"category_deleted": "Categorie verwijderd: {{categoryName}}",
|
||||
"feedback_settings_updated": "Feedbackinstellingen bijgewerkt",
|
||||
"feedback_moderated": "Feedback gemodereerd",
|
||||
"feedback_deleted": "Feedback verwijderd",
|
||||
"word_filter_added": "Woordfilter toegevoegd",
|
||||
"cms_page_updated": "CMS-pagina bijgewerkt: {{page}}",
|
||||
"email_resent": "Aanmaak-e-mail opnieuw verzonden voor: {{eventName}}",
|
||||
"settings_updated": "Instellingen bijgewerkt",
|
||||
"favicon_uploaded": "Favicon geüpload",
|
||||
"password_reset": "Wachtwoord gereset voor: {{eventName}}",
|
||||
"external_import_completed": "Externe media-import voltooid ({{imported}} geïmporteerd, {{skipped}} overgeslagen)",
|
||||
"photo_like": "Foto geliked in {{eventName}}",
|
||||
"photo_favorite": "Foto als favoriet gemarkeerd in {{eventName}}",
|
||||
"general_settings_updated": "Algemene instellingen bijgewerkt",
|
||||
"analytics_settings_updated": "Analyse-instellingen bijgewerkt",
|
||||
"security_settings_updated": "Beveiligingsinstellingen bijgewerkt",
|
||||
"event_renamed": "Evenement hernoemd: {{eventName}}",
|
||||
"photo_rating": "Foto beoordeeld in {{eventName}}",
|
||||
"photo_comment": "Foto becommentarieerd in {{eventName}}",
|
||||
"guest_feedback_like": "Gast heeft een foto geliked in {{eventName}}",
|
||||
"guest_feedback_favorite": "Gast heeft een foto als favoriet gemarkeerd in {{eventName}}",
|
||||
"guest_feedback_rating": "Gast heeft een foto beoordeeld in {{eventName}}",
|
||||
"guest_feedback_comment": "Gast heeft een opmerking geplaatst op een foto in {{eventName}}",
|
||||
"system_activity": "Systeemactiviteit: {{type}}",
|
||||
"unknown": "Onbekende activiteit"
|
||||
}
|
||||
},
|
||||
"acceptInvitation": {
|
||||
@@ -1799,7 +1901,13 @@
|
||||
},
|
||||
"storageDestination": "Opslagbestemming",
|
||||
"backupType": "{{type}} back-up",
|
||||
"noDestinationSet": "Geen bestemming ingesteld"
|
||||
"noDestinationSet": "Geen bestemming ingesteld",
|
||||
"healthStatus": {
|
||||
"warning": "Waarschuwing",
|
||||
"excellent": "Uitstekend",
|
||||
"good": "Goed",
|
||||
"critical": "Kritiek"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"enableBackup": "Geautomatiseerde back-ups inschakelen",
|
||||
@@ -2254,7 +2362,14 @@
|
||||
"hasComments": "Heeft opmerkingen",
|
||||
"showingPhotos": "Totaal foto's",
|
||||
"withRatings": "Met beoordelingen",
|
||||
"combineWith": "Combineren met"
|
||||
"combineWith": "Combineren met",
|
||||
"allPhotos": "Alle foto's",
|
||||
"anyRating": "Elke beoordeling",
|
||||
"oneStarPlus": "1+ ster",
|
||||
"twoStarsPlus": "2+ sterren",
|
||||
"threeStarsPlus": "3+ sterren",
|
||||
"fourStarsPlus": "4+ sterren",
|
||||
"fiveStarsOnly": "Alleen 5 sterren"
|
||||
},
|
||||
"export": {
|
||||
"button": "Exporteren",
|
||||
|
||||
@@ -161,7 +161,14 @@
|
||||
"navigation": {
|
||||
"dashboard": "Painel",
|
||||
"events": "Eventos",
|
||||
"settings": "Configurações"
|
||||
"settings": "Configurações",
|
||||
"archives": "Arquivos",
|
||||
"emailSettings": "Configurações de e-mail",
|
||||
"branding": "Identidade visual",
|
||||
"eventTypes": "Tipos de evento",
|
||||
"backup": "Backup e Restauração",
|
||||
"cmsPages": "Páginas CMS",
|
||||
"users": "Utilizadores"
|
||||
},
|
||||
"archives": {
|
||||
"title": "Arquivos",
|
||||
@@ -1312,7 +1319,37 @@
|
||||
"noTemplate": "Sem modelo",
|
||||
"noTemplateDescription": "Usar apenas as definições de tema sem modelo CSS",
|
||||
"templateSlot": "Posição {{slot}}",
|
||||
"eventCustomCSS": "CSS personalizado por evento"
|
||||
"eventCustomCSS": "CSS personalizado por evento",
|
||||
"layoutDescriptions": {
|
||||
"grid": "Grelha clássica com tamanhos de foto uniformes",
|
||||
"masonry": "Estilo Pinterest com alturas variadas",
|
||||
"carousel": "Apresentação a ecrã inteiro com navegação",
|
||||
"timeline": "Fotos organizadas por data",
|
||||
"mosaic": "Disposição artística com tamanhos mistos",
|
||||
"gallery-premium": "Tema claro elegante com hero e masonry (Beta)",
|
||||
"gallery-story": "Tema escuro cinemático com secções de cena (Beta)"
|
||||
},
|
||||
"headerStyleOptions": {
|
||||
"hero": "Imagem Hero",
|
||||
"standard": "Standard",
|
||||
"banner": "Banner",
|
||||
"minimal": "Mínimo",
|
||||
"none": "Sem cabeçalho"
|
||||
},
|
||||
"headerStyleDescriptions": {
|
||||
"hero": "Imagem de altura total com sobreposição de info do evento",
|
||||
"standard": "Cabeçalho compacto com detalhes do evento",
|
||||
"banner": "Cabeçalho standard com banner colorido acima",
|
||||
"minimal": "Cabeçalho compacto com informações essenciais",
|
||||
"none": "Ocultar cabeçalho completamente"
|
||||
},
|
||||
"dividerOptions": {
|
||||
"none": "Nenhum",
|
||||
"wave": "Onda",
|
||||
"straight": "Reto",
|
||||
"angle": "Ângulo",
|
||||
"curve": "Curva"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "Painel Admin",
|
||||
@@ -1326,7 +1363,10 @@
|
||||
"archivedEvents": "Eventos Arquivados",
|
||||
"systemHealth": "Saúde do Sistema",
|
||||
"health": {
|
||||
"checking": "Verificando..."
|
||||
"checking": "Verificando...",
|
||||
"healthy": "Saudável",
|
||||
"warning": "Aviso",
|
||||
"error": "Erro"
|
||||
},
|
||||
"updates": {
|
||||
"available": "Atualização Disponível",
|
||||
@@ -1490,8 +1530,70 @@
|
||||
"loadingDetail": "A carregar seleções…",
|
||||
"detail": {
|
||||
"noComments": "Sem comentários",
|
||||
"empty": "Sem seleções nesta categoria"
|
||||
"empty": "Sem seleções nesta categoria",
|
||||
"all": "Todos",
|
||||
"liked": "Gostou",
|
||||
"favorited": "Favorito",
|
||||
"rated": "Avaliado",
|
||||
"commented": "Comentado"
|
||||
},
|
||||
"inviteStatus": {
|
||||
"pending": "Pendente",
|
||||
"redeemed": "Utilizado",
|
||||
"revoked": "Revogado"
|
||||
}
|
||||
},
|
||||
"analytics": "Análise",
|
||||
"activities": {
|
||||
"event_created": "Novo evento criado: {{eventName}}",
|
||||
"photos_uploaded": "{{count}} fotos carregadas para {{eventName}}",
|
||||
"event_archived": "Evento arquivado: {{eventName}}",
|
||||
"archive_restored": "Arquivo restaurado: {{eventName}}",
|
||||
"archive_deleted": "Arquivo eliminado: {{eventName}}",
|
||||
"archive_downloaded": "Arquivo descarregado: {{eventName}}",
|
||||
"email_config_updated": "Configuração de e-mail atualizada",
|
||||
"email_template_updated": "Modelo de e-mail atualizado: {{template}}",
|
||||
"branding_updated": "Definições de identidade visual atualizadas",
|
||||
"theme_updated": "Definições de tema atualizadas",
|
||||
"bulk_download": "{{count}} fotos descarregadas de {{eventName}}",
|
||||
"gallery_password_entry": "Palavra-passe inserida para {{eventName}}",
|
||||
"expiration_warning_viewed": "Aviso de expiração visualizado para {{eventName}}",
|
||||
"event_updated": "Evento atualizado: {{eventName}}",
|
||||
"event_deleted": "Evento eliminado: {{eventName}}",
|
||||
"event_activated": "Evento ativado: {{eventName}}",
|
||||
"event_deactivated": "Evento desativado: {{eventName}}",
|
||||
"bulk_archive_completed": "Arquivamento em massa concluído",
|
||||
"photo_deleted": "Foto eliminada de {{eventName}}",
|
||||
"photos_bulk_deleted": "{{count}} fotos eliminadas de {{eventName}}",
|
||||
"password_changed": "Palavra-passe alterada",
|
||||
"admin_logout": "Admin {{actorName}} terminou sessão",
|
||||
"category_created": "Categoria criada: {{categoryName}}",
|
||||
"category_updated": "Categoria atualizada: {{categoryName}}",
|
||||
"category_deleted": "Categoria eliminada: {{categoryName}}",
|
||||
"feedback_settings_updated": "Definições de feedback atualizadas",
|
||||
"feedback_moderated": "Feedback moderado",
|
||||
"feedback_deleted": "Feedback eliminado",
|
||||
"word_filter_added": "Filtro de palavras adicionado",
|
||||
"cms_page_updated": "Página CMS atualizada: {{page}}",
|
||||
"email_resent": "E-mail de criação reenviado para: {{eventName}}",
|
||||
"settings_updated": "Definições atualizadas",
|
||||
"favicon_uploaded": "Favicon carregado",
|
||||
"password_reset": "Palavra-passe redefinida para: {{eventName}}",
|
||||
"external_import_completed": "Importação de média externo concluída ({{imported}} importados, {{skipped}} ignorados)",
|
||||
"photo_like": "Foto curtida em {{eventName}}",
|
||||
"photo_favorite": "Foto adicionada aos favoritos em {{eventName}}",
|
||||
"general_settings_updated": "Definições gerais atualizadas",
|
||||
"analytics_settings_updated": "Definições de análise atualizadas",
|
||||
"security_settings_updated": "Definições de segurança atualizadas",
|
||||
"event_renamed": "Evento renomeado: {{eventName}}",
|
||||
"photo_rating": "Foto avaliada em {{eventName}}",
|
||||
"photo_comment": "Foto comentada em {{eventName}}",
|
||||
"guest_feedback_like": "Convidado curtiu uma foto em {{eventName}}",
|
||||
"guest_feedback_favorite": "Convidado adicionou uma foto aos favoritos em {{eventName}}",
|
||||
"guest_feedback_rating": "Convidado avaliou uma foto em {{eventName}}",
|
||||
"guest_feedback_comment": "Convidado comentou uma foto em {{eventName}}",
|
||||
"system_activity": "Atividade do sistema: {{type}}",
|
||||
"unknown": "Atividade desconhecida"
|
||||
}
|
||||
},
|
||||
"acceptInvitation": {
|
||||
@@ -1824,7 +1926,13 @@
|
||||
},
|
||||
"storageDestination": "Destino do Armazenamento",
|
||||
"backupType": "Backup {{type}}",
|
||||
"noDestinationSet": "Nenhum destino definido"
|
||||
"noDestinationSet": "Nenhum destino definido",
|
||||
"healthStatus": {
|
||||
"warning": "Aviso",
|
||||
"excellent": "Excelente",
|
||||
"good": "Bom",
|
||||
"critical": "Crítico"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"enableBackup": "Ativar Backups Automáticos",
|
||||
@@ -2284,7 +2392,14 @@
|
||||
"hasComments": "Possui comentários",
|
||||
"showingPhotos": "Total de fotos",
|
||||
"withRatings": "Com avaliações",
|
||||
"combineWith": "Combinar com"
|
||||
"combineWith": "Combinar com",
|
||||
"allPhotos": "Todas as fotos",
|
||||
"anyRating": "Qualquer avaliação",
|
||||
"oneStarPlus": "1+ estrela",
|
||||
"twoStarsPlus": "2+ estrelas",
|
||||
"threeStarsPlus": "3+ estrelas",
|
||||
"fourStarsPlus": "4+ estrelas",
|
||||
"fiveStarsOnly": "Apenas 5 estrelas"
|
||||
},
|
||||
"adminLogin": {
|
||||
"title": "Login Administrativo",
|
||||
|
||||
@@ -164,7 +164,14 @@
|
||||
"navigation": {
|
||||
"dashboard": "Панель управления",
|
||||
"events": "События",
|
||||
"settings": "Настройки"
|
||||
"settings": "Настройки",
|
||||
"archives": "Архивы",
|
||||
"emailSettings": "Настройки e-mail",
|
||||
"branding": "Брендинг",
|
||||
"eventTypes": "Типы событий",
|
||||
"backup": "Резервное копирование",
|
||||
"cmsPages": "CMS-страницы",
|
||||
"users": "Пользователи"
|
||||
},
|
||||
"archives": {
|
||||
"title": "Архивы",
|
||||
@@ -1329,7 +1336,37 @@
|
||||
"noTemplate": "Без шаблона",
|
||||
"noTemplateDescription": "Использовать только настройки темы без CSS-шаблона",
|
||||
"templateSlot": "Слот {{slot}}",
|
||||
"eventCustomCSS": "Пользовательский CSS для события"
|
||||
"eventCustomCSS": "Пользовательский CSS для события",
|
||||
"layoutDescriptions": {
|
||||
"grid": "Классическая сетка с одинаковыми размерами фото",
|
||||
"masonry": "Макет в стиле Pinterest с разной высотой",
|
||||
"carousel": "Полноэкранное слайд-шоу с навигацией",
|
||||
"timeline": "Фото, упорядоченные по дате",
|
||||
"mosaic": "Художественный макет с разными размерами",
|
||||
"gallery-premium": "Элегантная светлая тема с hero и masonry (Бета)",
|
||||
"gallery-story": "Кинематографическая тёмная тема с разделами сцен (Бета)"
|
||||
},
|
||||
"headerStyleOptions": {
|
||||
"hero": "Изображение Hero",
|
||||
"standard": "Стандартный",
|
||||
"banner": "Баннер",
|
||||
"minimal": "Минимальный",
|
||||
"none": "Без заголовка"
|
||||
},
|
||||
"headerStyleDescriptions": {
|
||||
"hero": "Изображение в полную высоту с наложением информации о событии",
|
||||
"standard": "Компактный встроенный заголовок с деталями события",
|
||||
"banner": "Стандартный заголовок плюс цветной баннер сверху",
|
||||
"minimal": "Компактный заголовок с основной информацией",
|
||||
"none": "Полностью скрыть заголовок"
|
||||
},
|
||||
"dividerOptions": {
|
||||
"none": "Нет",
|
||||
"wave": "Волна",
|
||||
"straight": "Прямой",
|
||||
"angle": "Угол",
|
||||
"curve": "Кривая"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"title": "Панель администратора",
|
||||
@@ -1343,7 +1380,10 @@
|
||||
"archivedEvents": "Архивированных событий",
|
||||
"systemHealth": "Состояние системы",
|
||||
"health": {
|
||||
"checking": "Проверка..."
|
||||
"checking": "Проверка...",
|
||||
"healthy": "Исправно",
|
||||
"warning": "Предупреждение",
|
||||
"error": "Ошибка"
|
||||
},
|
||||
"updates": {
|
||||
"available": "Доступно обновление",
|
||||
@@ -1515,8 +1555,70 @@
|
||||
"loadingDetail": "Загрузка выборок…",
|
||||
"detail": {
|
||||
"noComments": "Нет комментариев",
|
||||
"empty": "Нет выборок в этой категории"
|
||||
"empty": "Нет выборок в этой категории",
|
||||
"all": "Все",
|
||||
"liked": "Понравилось",
|
||||
"favorited": "В избранном",
|
||||
"rated": "Оценено",
|
||||
"commented": "Прокомментировано"
|
||||
},
|
||||
"inviteStatus": {
|
||||
"pending": "Ожидает",
|
||||
"redeemed": "Использовано",
|
||||
"revoked": "Отозвано"
|
||||
}
|
||||
},
|
||||
"analytics": "Аналитика",
|
||||
"activities": {
|
||||
"event_created": "Новое событие создано: {{eventName}}",
|
||||
"photos_uploaded": "{{count}} фото загружено в {{eventName}}",
|
||||
"event_archived": "Событие архивировано: {{eventName}}",
|
||||
"archive_restored": "Архив восстановлен: {{eventName}}",
|
||||
"archive_deleted": "Архив удалён: {{eventName}}",
|
||||
"archive_downloaded": "Архив скачан: {{eventName}}",
|
||||
"email_config_updated": "Конфигурация e-mail обновлена",
|
||||
"email_template_updated": "Шаблон e-mail обновлён: {{template}}",
|
||||
"branding_updated": "Настройки брендинга обновлены",
|
||||
"theme_updated": "Настройки темы обновлены",
|
||||
"bulk_download": "{{count}} фото скачано из {{eventName}}",
|
||||
"gallery_password_entry": "Введён пароль для {{eventName}}",
|
||||
"expiration_warning_viewed": "Предупреждение об истечении просмотрено для {{eventName}}",
|
||||
"event_updated": "Событие обновлено: {{eventName}}",
|
||||
"event_deleted": "Событие удалено: {{eventName}}",
|
||||
"event_activated": "Событие активировано: {{eventName}}",
|
||||
"event_deactivated": "Событие деактивировано: {{eventName}}",
|
||||
"bulk_archive_completed": "Массовое архивирование завершено",
|
||||
"photo_deleted": "Фото удалено из {{eventName}}",
|
||||
"photos_bulk_deleted": "{{count}} фото удалено из {{eventName}}",
|
||||
"password_changed": "Пароль изменён",
|
||||
"admin_logout": "Администратор {{actorName}} вышел из системы",
|
||||
"category_created": "Категория создана: {{categoryName}}",
|
||||
"category_updated": "Категория обновлена: {{categoryName}}",
|
||||
"category_deleted": "Категория удалена: {{categoryName}}",
|
||||
"feedback_settings_updated": "Настройки отзывов обновлены",
|
||||
"feedback_moderated": "Отзыв промодерирован",
|
||||
"feedback_deleted": "Отзыв удалён",
|
||||
"word_filter_added": "Фильтр слов добавлен",
|
||||
"cms_page_updated": "Страница CMS обновлена: {{page}}",
|
||||
"email_resent": "E-mail создания повторно отправлен для: {{eventName}}",
|
||||
"settings_updated": "Настройки обновлены",
|
||||
"favicon_uploaded": "Favicon загружен",
|
||||
"password_reset": "Пароль сброшен для: {{eventName}}",
|
||||
"external_import_completed": "Импорт внешних медиафайлов завершён ({{imported}} импортировано, {{skipped}} пропущено)",
|
||||
"photo_like": "Фото понравилось в {{eventName}}",
|
||||
"photo_favorite": "Фото добавлено в избранное в {{eventName}}",
|
||||
"general_settings_updated": "Общие настройки обновлены",
|
||||
"analytics_settings_updated": "Настройки аналитики обновлены",
|
||||
"security_settings_updated": "Настройки безопасности обновлены",
|
||||
"event_renamed": "Событие переименовано: {{eventName}}",
|
||||
"photo_rating": "Фото оценено в {{eventName}}",
|
||||
"photo_comment": "Фото прокомментировано в {{eventName}}",
|
||||
"guest_feedback_like": "Гость поставил лайк фото в {{eventName}}",
|
||||
"guest_feedback_favorite": "Гость добавил фото в избранное в {{eventName}}",
|
||||
"guest_feedback_rating": "Гость оценил фото в {{eventName}}",
|
||||
"guest_feedback_comment": "Гость прокомментировал фото в {{eventName}}",
|
||||
"system_activity": "Системная активность: {{type}}",
|
||||
"unknown": "Неизвестная активность"
|
||||
}
|
||||
},
|
||||
"acceptInvitation": {
|
||||
@@ -1849,7 +1951,13 @@
|
||||
},
|
||||
"storageDestination": "Место хранения",
|
||||
"backupType": "Резервная копия {{type}}",
|
||||
"noDestinationSet": "Место хранения не задано"
|
||||
"noDestinationSet": "Место хранения не задано",
|
||||
"healthStatus": {
|
||||
"warning": "Предупреждение",
|
||||
"excellent": "Отлично",
|
||||
"good": "Хорошо",
|
||||
"critical": "Критически"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"enableBackup": "Включить автоматическое резервное копирование",
|
||||
@@ -2314,7 +2422,14 @@
|
||||
"hasComments": "Есть комментарии",
|
||||
"showingPhotos": "Всего фото",
|
||||
"withRatings": "С оценками",
|
||||
"combineWith": "Объединить с"
|
||||
"combineWith": "Объединить с",
|
||||
"allPhotos": "Все фото",
|
||||
"anyRating": "Любая оценка",
|
||||
"oneStarPlus": "1+ звезда",
|
||||
"twoStarsPlus": "2+ звезды",
|
||||
"threeStarsPlus": "3+ звезды",
|
||||
"fourStarsPlus": "4+ звезды",
|
||||
"fiveStarsOnly": "Только 5 звёзд"
|
||||
},
|
||||
"adminLogin": {
|
||||
"title": "Вход для администратора",
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Button, Card, Loading } from '../../components/common';
|
||||
import { UpdateNotification } from '../../components/admin/UpdateNotification';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { eventsService } from '../../services/events.service';
|
||||
import { adminService } from '../../services/admin.service';
|
||||
import { adminService, ActivityType } from '../../services/admin.service';
|
||||
|
||||
interface StatCard {
|
||||
title: string;
|
||||
@@ -246,8 +246,8 @@ export const AdminDashboard: React.FC = () => {
|
||||
) : (
|
||||
recentActivity.slice(0, 5).map((activity) => {
|
||||
// Get color based on activity type
|
||||
const getActivityColor = (type: string) => {
|
||||
const colors: Record<string, string> = {
|
||||
const getActivityColor = (type: ActivityType) => {
|
||||
const colors: Partial<Record<ActivityType, string>> = {
|
||||
'event_created': 'bg-green-500',
|
||||
'photos_uploaded': 'bg-blue-500',
|
||||
'event_archived': 'bg-purple-500',
|
||||
@@ -264,21 +264,20 @@ export const AdminDashboard: React.FC = () => {
|
||||
|
||||
// Format activity message with translations
|
||||
const getActivityMessage = (): string => {
|
||||
const translationKey = `admin.activities.${activity.type}`;
|
||||
const params: Record<string, any> = {
|
||||
eventName: activity.eventName || t('common.unknown'),
|
||||
count: activity.metadata?.count || 0,
|
||||
template: activity.metadata?.template_key || '',
|
||||
categoryName: activity.metadata?.category_name || ''
|
||||
};
|
||||
const translated = t(`admin.activities.${activity.type}`, params);
|
||||
|
||||
// Translate; if key missing i18n returns the key string itself
|
||||
const translated = t(translationKey, params) as string;
|
||||
if (!translated || translated === translationKey) {
|
||||
if (!translated || translated === `admin.activities.${activity.type}`) {
|
||||
// Fallback: format a readable English message
|
||||
return adminService.formatActivityMessage(activity);
|
||||
}
|
||||
return translated;
|
||||
return translated as string;
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
export const BackupManagement: ComponentType<any>;
|
||||
+29
-45
@@ -7,20 +7,9 @@ import {
|
||||
RefreshCw,
|
||||
AlertCircle,
|
||||
CheckCircle,
|
||||
Calendar,
|
||||
Database,
|
||||
FileArchive,
|
||||
Cloud,
|
||||
Server,
|
||||
Loader2,
|
||||
Info,
|
||||
Shield,
|
||||
Clock,
|
||||
Download,
|
||||
Upload,
|
||||
Trash2,
|
||||
Search,
|
||||
Filter
|
||||
Loader2,
|
||||
Shield,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
@@ -34,41 +23,37 @@ import { BackupHistory } from '../../components/admin/BackupHistory';
|
||||
import { RestoreWizard } from '../../components/admin/RestoreWizard';
|
||||
import { api } from '../../config/api';
|
||||
|
||||
// Tab components will be defined inside the component to use translations
|
||||
type TabId = 'dashboard' | 'configuration' | 'history' | 'restore';
|
||||
|
||||
export const BackupManagement = () => {
|
||||
const [activeTab, setActiveTab] = useState('dashboard');
|
||||
export const BackupManagement: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<TabId>('dashboard');
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Tab components with translations
|
||||
const tabs = [
|
||||
{ id: 'dashboard', label: t('backup.tabs.dashboard'), icon: HardDrive },
|
||||
{ id: 'configuration', label: t('backup.tabs.configuration'), icon: Settings },
|
||||
{ id: 'history', label: t('backup.tabs.history'), icon: History },
|
||||
{ id: 'restore', label: t('backup.tabs.restore'), icon: RefreshCw }
|
||||
{ id: 'dashboard' as const, label: t('backup.tabs.dashboard'), icon: HardDrive },
|
||||
{ id: 'configuration' as const, label: t('backup.tabs.configuration'), icon: Settings },
|
||||
{ id: 'history' as const, label: t('backup.tabs.history'), icon: History },
|
||||
{ id: 'restore' as const, label: t('backup.tabs.restore'), icon: RefreshCw },
|
||||
];
|
||||
|
||||
// Fetch backup status
|
||||
const { data: backupStatus, isLoading: statusLoading } = useQuery({
|
||||
queryKey: ['backup-status'],
|
||||
queryFn: async () => {
|
||||
const response = await api.get('/admin/backup/status');
|
||||
return response.data;
|
||||
},
|
||||
refetchInterval: 10000 // Refresh every 10 seconds
|
||||
refetchInterval: 10000,
|
||||
});
|
||||
|
||||
// Fetch backup configuration
|
||||
const { data: backupConfig, isLoading: configLoading } = useQuery({
|
||||
queryKey: ['backup-config'],
|
||||
queryFn: async () => {
|
||||
const response = await api.get('/admin/backup/config');
|
||||
return response.data;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Trigger manual backup
|
||||
const manualBackupMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await api.post('/admin/backup/run');
|
||||
@@ -78,15 +63,14 @@ export const BackupManagement = () => {
|
||||
toast.success(t('backup.messages.backupStarted'));
|
||||
queryClient.invalidateQueries({ queryKey: ['backup-status'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: (error: any) => {
|
||||
const message = error.response?.data?.error || t('backup.messages.backupFailed');
|
||||
toast.error(message);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Update configuration
|
||||
const updateConfigMutation = useMutation({
|
||||
mutationFn: async (config) => {
|
||||
mutationFn: async (config: unknown) => {
|
||||
const response = await api.put('/admin/backup/config', config);
|
||||
return response.data;
|
||||
},
|
||||
@@ -94,10 +78,10 @@ export const BackupManagement = () => {
|
||||
toast.success(t('backup.messages.configUpdated'));
|
||||
queryClient.invalidateQueries({ queryKey: ['backup-config'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: (error: any) => {
|
||||
const message = error.response?.data?.error || t('backup.messages.configUpdateFailed');
|
||||
toast.error(message);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (statusLoading || configLoading) {
|
||||
@@ -142,7 +126,7 @@ export const BackupManagement = () => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{backupConfig?.backup_enabled && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Clock className="h-5 w-5 text-neutral-400" />
|
||||
@@ -156,11 +140,11 @@ export const BackupManagement = () => {
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button
|
||||
onClick={() => manualBackupMutation.mutate()}
|
||||
disabled={backupStatus?.isRunning || manualBackupMutation.isLoading}
|
||||
disabled={backupStatus?.isRunning || manualBackupMutation.isPending}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
{manualBackupMutation.isLoading ? (
|
||||
{manualBackupMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
{t('backup.actions.starting')}
|
||||
@@ -172,7 +156,7 @@ export const BackupManagement = () => {
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
|
||||
<div className={`flex items-center space-x-1 px-3 py-1 rounded-full text-sm font-medium ${
|
||||
backupConfig?.backup_enabled
|
||||
? 'bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-300'
|
||||
@@ -213,30 +197,30 @@ export const BackupManagement = () => {
|
||||
{/* Tab Content */}
|
||||
<div className="mt-6">
|
||||
{activeTab === 'dashboard' && (
|
||||
<BackupDashboard
|
||||
status={backupStatus}
|
||||
<BackupDashboard
|
||||
status={backupStatus}
|
||||
config={backupConfig}
|
||||
onRunBackup={() => manualBackupMutation.mutate()}
|
||||
isBackupRunning={backupStatus?.isRunning || manualBackupMutation.isLoading}
|
||||
isBackupRunning={backupStatus?.isRunning || manualBackupMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
{activeTab === 'configuration' && (
|
||||
<BackupConfiguration
|
||||
config={backupConfig}
|
||||
onSave={(newConfig) => updateConfigMutation.mutate(newConfig)}
|
||||
isSaving={updateConfigMutation.isLoading}
|
||||
onSave={(newConfig: unknown) => updateConfigMutation.mutate(newConfig)}
|
||||
isSaving={updateConfigMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
{activeTab === 'history' && (
|
||||
<BackupHistory />
|
||||
)}
|
||||
|
||||
|
||||
{activeTab === 'restore' && (
|
||||
<RestoreWizard />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -38,9 +38,60 @@ export interface SystemHealth {
|
||||
};
|
||||
}
|
||||
|
||||
export type ActivityType =
|
||||
| "event_created"
|
||||
| "photos_uploaded"
|
||||
| "event_archived"
|
||||
| "archive_restored"
|
||||
| "archive_deleted"
|
||||
| "archive_downloaded"
|
||||
| "email_config_updated"
|
||||
| "email_template_updated"
|
||||
| "branding_updated"
|
||||
| "theme_updated"
|
||||
| "bulk_download"
|
||||
| "gallery_password_entry"
|
||||
| "expiration_warning_viewed"
|
||||
| "feedback_settings_updated"
|
||||
| "feedback_moderated"
|
||||
| "feedback_deleted"
|
||||
| "photo_like"
|
||||
| "photo_favorite"
|
||||
| "photo_rating"
|
||||
| "photo_comment"
|
||||
| "guest_feedback_like"
|
||||
| "guest_feedback_favorite"
|
||||
| "guest_feedback_rating"
|
||||
| "guest_feedback_comment"
|
||||
| "word_filter_added"
|
||||
| "external_import_completed"
|
||||
| "bulk_archive_completed"
|
||||
| "event_activated"
|
||||
| "event_deactivated"
|
||||
| "photo_deleted"
|
||||
| "photos_bulk_deleted"
|
||||
| "settings_updated"
|
||||
| "event_updated"
|
||||
| "event_renamed"
|
||||
| "event_deleted"
|
||||
| "password_changed"
|
||||
| "email_resent"
|
||||
| "category_created"
|
||||
| "category_updated"
|
||||
| "category_deleted"
|
||||
| "general_settings_updated"
|
||||
| "favicon_uploaded"
|
||||
| "analytics_settings_updated"
|
||||
| "cms_page_updated"
|
||||
| "security_settings_updated"
|
||||
| "password_reset"
|
||||
| "admin_logout"
|
||||
| "system_activity"
|
||||
| "unknown"
|
||||
|
||||
export interface Activity {
|
||||
id: number;
|
||||
type: string;
|
||||
type: ActivityType;
|
||||
actorType: string;
|
||||
actorName: string;
|
||||
eventName?: string;
|
||||
|
||||
Reference in New Issue
Block a user