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 { defineConfig } from 'i18next-cli';
|
||||||
|
import { typescriptPlugin } from "./scripts/i18nextExtractionHelper";
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
locales: ['en', 'de', 'nl', 'pt', 'ru', 'fr'],
|
locales: ['en', 'de', 'nl', 'pt', 'ru', 'fr'],
|
||||||
@@ -18,6 +20,7 @@ export default defineConfig({
|
|||||||
preserveContextVariants: true,
|
preserveContextVariants: true,
|
||||||
|
|
||||||
indentation: 2,
|
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/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.1.0",
|
"@testing-library/react": "^16.1.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
|
"@types/node": "^25.6.2",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@vitejs/plugin-react": "^4.5.3",
|
"@vitejs/plugin-react": "^4.5.3",
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
"globals": "^16.2.0",
|
"globals": "^16.2.0",
|
||||||
"i18next-cli": "^1.56.11",
|
"i18next-cli": "^1.56.11",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
|
"memfs": "^4.57.2",
|
||||||
"postcss": "^8.5.10",
|
"postcss": "^8.5.10",
|
||||||
"tailwindcss": "^3.3.0",
|
"tailwindcss": "^3.3.0",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
@@ -1618,6 +1620,436 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@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": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -3038,6 +3470,16 @@
|
|||||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
@@ -5100,6 +5542,23 @@
|
|||||||
"node": ">=10.13.0"
|
"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": {
|
"node_modules/glob/node_modules/balanced-match": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
@@ -5298,6 +5757,16 @@
|
|||||||
"node": ">=18.18.0"
|
"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": {
|
"node_modules/i18next": {
|
||||||
"version": "25.7.3",
|
"version": "25.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.3.tgz",
|
||||||
@@ -6190,6 +6659,36 @@
|
|||||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
@@ -8003,6 +8502,23 @@
|
|||||||
"node": ">=0.8"
|
"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": {
|
"node_modules/tinybench": {
|
||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||||
@@ -8125,6 +8641,23 @@
|
|||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
"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==",
|
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/unicorn-magic": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
"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/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.1.0",
|
"@testing-library/react": "^16.1.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
|
"@types/node": "^25.6.2",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@vitejs/plugin-react": "^4.5.3",
|
"@vitejs/plugin-react": "^4.5.3",
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
"globals": "^16.2.0",
|
"globals": "^16.2.0",
|
||||||
"i18next-cli": "^1.56.11",
|
"i18next-cli": "^1.56.11",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
|
"memfs": "^4.57.2",
|
||||||
"postcss": "^8.5.10",
|
"postcss": "^8.5.10",
|
||||||
"tailwindcss": "^3.3.0",
|
"tailwindcss": "^3.3.0",
|
||||||
"typescript": "~5.8.3",
|
"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;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavItem {
|
const navigation = [
|
||||||
nameKey: string;
|
{ nameKey: 'navigation.dashboard', href: '/admin/dashboard', icon: LayoutDashboard, permission: false },
|
||||||
href: string;
|
|
||||||
icon: React.ComponentType<{ className?: string }>;
|
|
||||||
permission?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const navigation: NavItem[] = [
|
|
||||||
{ nameKey: 'navigation.dashboard', href: '/admin/dashboard', icon: LayoutDashboard },
|
|
||||||
{ nameKey: 'navigation.events', href: '/admin/events', icon: Calendar, permission: 'events.view' },
|
{ nameKey: 'navigation.events', href: '/admin/events', icon: Calendar, permission: 'events.view' },
|
||||||
{ nameKey: 'navigation.archives', href: '/admin/archives', icon: Archive, permission: 'archives.view' },
|
{ nameKey: 'navigation.archives', href: '/admin/archives', icon: Archive, permission: 'archives.view' },
|
||||||
{ nameKey: 'admin.analytics', href: '/admin/analytics', icon: BarChart3, permission: 'analytics.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.backup', href: '/admin/backup', icon: HardDrive, permission: 'backup.view' },
|
||||||
{ nameKey: 'navigation.cmsPages', href: '/admin/cms', icon: FileText, permission: 'cms.view' },
|
{ nameKey: 'navigation.cmsPages', href: '/admin/cms', icon: FileText, permission: 'cms.view' },
|
||||||
{ nameKey: 'navigation.users', href: '/admin/users', icon: Users, permission: 'users.view' },
|
{ nameKey: 'navigation.users', href: '/admin/users', icon: Users, permission: 'users.view' },
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
export const AdminSidebar: React.FC<AdminSidebarProps> = ({ isOpen, onClose }) => {
|
export const AdminSidebar: React.FC<AdminSidebarProps> = ({ isOpen, onClose }) => {
|
||||||
const location = useLocation();
|
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,
|
Server,
|
||||||
Cloud,
|
Cloud,
|
||||||
HardDrive,
|
HardDrive,
|
||||||
Clock,
|
|
||||||
Calendar,
|
|
||||||
Shield,
|
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Info,
|
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
Wifi,
|
Wifi,
|
||||||
CheckCircle,
|
|
||||||
XCircle,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
FolderOpen,
|
|
||||||
Database,
|
Database,
|
||||||
Image,
|
Image,
|
||||||
FileArchive
|
FileArchive
|
||||||
@@ -24,26 +17,58 @@ import {
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { Button, Card, Input } from '../common';
|
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 { t } = useTranslation();
|
||||||
|
|
||||||
const destinationTypes = [
|
const destinationTypes = [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local' as const,
|
||||||
name: t('backup.configuration.destinationTypes.local.name'),
|
name: t('backup.configuration.destinationTypes.local.name'),
|
||||||
icon: HardDrive,
|
icon: HardDrive,
|
||||||
description: t('backup.configuration.destinationTypes.local.description'),
|
description: t('backup.configuration.destinationTypes.local.description'),
|
||||||
fields: ['backup_destination_path']
|
fields: ['backup_destination_path']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'rsync',
|
id: 'rsync' as const,
|
||||||
name: t('backup.configuration.destinationTypes.rsync.name'),
|
name: t('backup.configuration.destinationTypes.rsync.name'),
|
||||||
icon: Server,
|
icon: Server,
|
||||||
description: t('backup.configuration.destinationTypes.rsync.description'),
|
description: t('backup.configuration.destinationTypes.rsync.description'),
|
||||||
fields: ['backup_rsync_host', 'backup_rsync_user', 'backup_rsync_path', 'backup_rsync_ssh_key']
|
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'),
|
name: t('backup.configuration.destinationTypes.s3.name'),
|
||||||
icon: Cloud,
|
icon: Cloud,
|
||||||
description: t('backup.configuration.destinationTypes.s3.description'),
|
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: 'weekly', label: t('backup.configuration.schedule.options.weekly') },
|
||||||
{ value: 'custom', label: t('backup.configuration.schedule.options.custom') }
|
{ value: 'custom', label: t('backup.configuration.schedule.options.custom') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState<BackupFormData>({
|
||||||
backup_enabled: false,
|
backup_enabled: false,
|
||||||
backup_destination_type: 'local',
|
backup_destination_type: 'local',
|
||||||
backup_destination_path: '',
|
backup_destination_path: '',
|
||||||
@@ -101,33 +126,32 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
|||||||
}
|
}
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
const handleChange = <K extends keyof BackupFormData>(field: K, value: BackupFormData[K]) => {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value
|
[field]: value
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Validate required fields
|
const destinationType = destinationTypes.find(dt => dt.id === formData.backup_destination_type);
|
||||||
const destinationType = destinationTypes.find(t => t.id === formData.backup_destination_type);
|
const missingFields: string[] = [];
|
||||||
const missingFields = [];
|
|
||||||
|
|
||||||
if (formData.backup_enabled && destinationType) {
|
if (formData.backup_enabled && destinationType) {
|
||||||
destinationType.fields.forEach(field => {
|
destinationType.fields.forEach(field => {
|
||||||
if (!formData[field] && !field.includes('optional')) {
|
if (!formData[field as keyof BackupFormData] && !field.includes('optional')) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (missingFields.length > 0) {
|
if (missingFields.length > 0) {
|
||||||
toast.error(t('backup.configuration.messages.requiredFields'));
|
toast.error(t('backup.configuration.messages.requiredFields'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(formData);
|
onSave(formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -138,14 +162,12 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
toast.success(t('backup.configuration.messages.connectionSuccess'));
|
toast.success(t('backup.configuration.messages.connectionSuccess'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(t('backup.configuration.messages.connectionFailed') + ': ' + error.message);
|
toast.error(t('backup.configuration.messages.connectionFailed') + ': ' + (error as Error).message);
|
||||||
} finally {
|
} finally {
|
||||||
setTestingConnection(false);
|
setTestingConnection(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedDestination = destinationTypes.find(t => t.id === formData.backup_destination_type);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
{/* Enable/Disable Toggle */}
|
{/* Enable/Disable Toggle */}
|
||||||
@@ -399,7 +421,7 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
|||||||
{/* Schedule Configuration */}
|
{/* Schedule Configuration */}
|
||||||
<Card className="p-6">
|
<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>
|
<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 className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">
|
<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 */}
|
{/* Backup Content Selection */}
|
||||||
<Card className="p-6">
|
<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>
|
<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">
|
<div className="space-y-3">
|
||||||
<label className="flex items-center">
|
<label className="flex items-center">
|
||||||
<input
|
<input
|
||||||
@@ -527,7 +549,7 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
|||||||
{/* Advanced Options */}
|
{/* Advanced Options */}
|
||||||
<Card className="p-6">
|
<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>
|
<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">
|
<div className="space-y-4">
|
||||||
<label className="flex items-center">
|
<label className="flex items-center">
|
||||||
<input
|
<input
|
||||||
@@ -608,4 +630,4 @@ export const BackupConfiguration = ({ config, onSave, isSaving }) => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import type { ComponentType } from 'react';
|
|
||||||
|
|
||||||
export const BackupDashboard: ComponentType<any>;
|
|
||||||
+76
-26
@@ -8,11 +8,9 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
TrendingUp,
|
|
||||||
Shield,
|
Shield,
|
||||||
Server,
|
Server,
|
||||||
Cloud,
|
Cloud,
|
||||||
Calendar,
|
|
||||||
Play,
|
Play,
|
||||||
Loader2,
|
Loader2,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
@@ -21,7 +19,59 @@ import {
|
|||||||
import { format, formatDistanceToNow } from 'date-fns';
|
import { format, formatDistanceToNow } from 'date-fns';
|
||||||
import { Card, Button } from '../common';
|
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">
|
<Card className="p-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -38,7 +88,7 @@ const StatCard = ({ icon: Icon, label, value, color = 'blue', subtext }) => (
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatBytes = (bytes) => {
|
const formatBytes = (bytes: number): string => {
|
||||||
if (!bytes) return '0 B';
|
if (!bytes) return '0 B';
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
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]}`;
|
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 { t } = useTranslation();
|
||||||
const lastBackup = status?.lastBackup;
|
const lastBackup = status?.lastBackup;
|
||||||
const statistics = lastBackup?.statistics || {};
|
const statistics = lastBackup?.statistics ?? {};
|
||||||
const isConfigured = config && config.backup_destination_type;
|
const isConfigured = config && config.backup_destination_type;
|
||||||
const isEnabled = config?.backup_enabled;
|
const isEnabled = config?.backup_enabled;
|
||||||
|
|
||||||
// Calculate backup health score
|
const getHealthScore = (): { score: number; status: HealthStatus; message: string } => {
|
||||||
const getHealthScore = () => {
|
|
||||||
if (!lastBackup) return { score: 0, status: 'critical', message: t('backup.dashboard.healthMessages.noBackups') };
|
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') {
|
if (lastBackup.status === 'failed') {
|
||||||
return { score: 0, status: 'critical', message: t('backup.dashboard.healthMessages.lastBackupFailed') };
|
return { score: 0, status: 'critical', message: t('backup.dashboard.healthMessages.lastBackupFailed') };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hoursSinceBackup < 24) {
|
if (hoursSinceBackup < 24) {
|
||||||
return { score: 100, status: 'excellent', message: t('backup.dashboard.healthMessages.upToDate') };
|
return { score: 100, status: 'excellent', message: t('backup.dashboard.healthMessages.upToDate') };
|
||||||
} else if (hoursSinceBackup < 48) {
|
} else if (hoursSinceBackup < 48) {
|
||||||
return { score: 75, status: 'good', message: t('backup.dashboard.healthMessages.recent') };
|
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') };
|
return { score: 50, status: 'warning', message: t('backup.dashboard.healthMessages.gettingOld') };
|
||||||
} else {
|
} else {
|
||||||
return { score: 25, status: 'critical', message: t('backup.dashboard.healthMessages.outdated') };
|
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 health = getHealthScore();
|
||||||
const healthColors = {
|
|
||||||
excellent: 'green',
|
|
||||||
good: 'blue',
|
|
||||||
warning: 'amber',
|
|
||||||
critical: 'red'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -109,7 +159,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
|||||||
{t(`backup.dashboard.healthStatus.${health.status}`)}
|
{t(`backup.dashboard.healthStatus.${health.status}`)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="relative w-24 h-24">
|
<div className="relative w-24 h-24">
|
||||||
<svg className="w-24 h-24 transform -rotate-90">
|
<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>
|
<span className="text-2xl font-bold text-neutral-900 dark:text-neutral-100">{health.score}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-neutral-700 dark:text-neutral-300 font-medium">{health.message}</p>
|
<p className="text-neutral-700 dark:text-neutral-300 font-medium">{health.message}</p>
|
||||||
{lastBackup && (
|
{lastBackup && (
|
||||||
@@ -145,7 +195,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
|||||||
Last successful backup: {formatDistanceToNow(new Date(lastBackup.created_at), { addSuffix: true })}
|
Last successful backup: {formatDistanceToNow(new Date(lastBackup.created_at), { addSuffix: true })}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={onRunBackup}
|
onClick={onRunBackup}
|
||||||
disabled={!isConfigured || !isEnabled || isBackupRunning}
|
disabled={!isConfigured || !isEnabled || isBackupRunning}
|
||||||
@@ -177,7 +227,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
|||||||
color="blue"
|
color="blue"
|
||||||
subtext={lastBackup ? `${t('backup.dashboard.stats.last')}: ${format(new Date(lastBackup.created_at), 'PP')}` : t('backup.dashboard.stats.noBackupsYet')}
|
subtext={lastBackup ? `${t('backup.dashboard.stats.last')}: ${format(new Date(lastBackup.created_at), 'PP')}` : t('backup.dashboard.stats.noBackupsYet')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={HardDrive}
|
icon={HardDrive}
|
||||||
label={t('backup.dashboard.stats.backupSize')}
|
label={t('backup.dashboard.stats.backupSize')}
|
||||||
@@ -185,7 +235,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
|||||||
color="green"
|
color="green"
|
||||||
subtext={`${statistics.files_processed || 0} ${t('backup.dashboard.stats.files')}`}
|
subtext={`${statistics.files_processed || 0} ${t('backup.dashboard.stats.files')}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={Clock}
|
icon={Clock}
|
||||||
label={t('backup.dashboard.stats.lastDuration')}
|
label={t('backup.dashboard.stats.lastDuration')}
|
||||||
@@ -193,7 +243,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
|||||||
color="purple"
|
color="purple"
|
||||||
subtext={lastBackup ? format(new Date(lastBackup.created_at), 'p') : ''}
|
subtext={lastBackup ? format(new Date(lastBackup.created_at), 'p') : ''}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
icon={Shield}
|
icon={Shield}
|
||||||
label={t('backup.dashboard.stats.backupStatus')}
|
label={t('backup.dashboard.stats.backupStatus')}
|
||||||
@@ -324,4 +374,4 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -19,7 +19,7 @@ const RATING_OPTIONS = [
|
|||||||
{ value: 3, label: 'filter.threeStarsPlus' },
|
{ value: 3, label: 'filter.threeStarsPlus' },
|
||||||
{ value: 4, label: 'filter.fourStarsPlus' },
|
{ value: 4, label: 'filter.fourStarsPlus' },
|
||||||
{ value: 5, label: 'filter.fiveStarsOnly' },
|
{ value: 5, label: 'filter.fiveStarsOnly' },
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
export const PhotoFilterPanel: React.FC<PhotoFilterPanelProps> = ({
|
export const PhotoFilterPanel: React.FC<PhotoFilterPanelProps> = ({
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
+472
-357
@@ -158,7 +158,14 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"events": "Veranstaltungen",
|
"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": {
|
"eventTypes": {
|
||||||
"title": "Veranstaltungsarten",
|
"title": "Veranstaltungsarten",
|
||||||
@@ -275,6 +282,12 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"runBackupNow": "Backup jetzt ausführen",
|
"runBackupNow": "Backup jetzt ausführen",
|
||||||
"running": "Wird ausgeführt..."
|
"running": "Wird ausgeführt..."
|
||||||
|
},
|
||||||
|
"healthStatus": {
|
||||||
|
"warning": "Warnung",
|
||||||
|
"excellent": "Ausgezeichnet",
|
||||||
|
"good": "Gut",
|
||||||
|
"critical": "Kritisch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
@@ -634,51 +647,51 @@
|
|||||||
"anonymous": "Anonym"
|
"anonymous": "Anonym"
|
||||||
},
|
},
|
||||||
"favorites": "Favoriten",
|
"favorites": "Favoriten",
|
||||||
"expiresIn_one": "",
|
"expiresIn_one": "Galerie läuft in {{count}} Tag ab",
|
||||||
"expiresIn_other": "",
|
"expiresIn_other": "Galerie läuft in {{count}} Tagen ab",
|
||||||
"downloading_one": "",
|
"downloading_one": "1 Foto wird heruntergeladen…",
|
||||||
"downloading_other": "",
|
"downloading_other": "{{count}} Fotos werden heruntergeladen…",
|
||||||
"photosSelected_one": "",
|
"photosSelected_one": "{{count}} Foto ausgewählt",
|
||||||
"photosSelected_other": "",
|
"photosSelected_other": "{{count}} Fotos ausgewählt",
|
||||||
"downloadSelected_one": "",
|
"downloadSelected_one": "{{count}} Foto herunterladen",
|
||||||
"downloadSelected_other": "",
|
"downloadSelected_other": "{{count}} Fotos herunterladen",
|
||||||
"guestRecovery": {
|
"guestRecovery": {
|
||||||
"invalidEmail": "",
|
"invalidEmail": "Gültige E-Mail-Adresse eingeben",
|
||||||
"codeSent": "",
|
"codeSent": "Prüfen Sie Ihren Posteingang auf einen Bestätigungscode.",
|
||||||
"requestError": "",
|
"requestError": "Code konnte nicht gesendet werden. Bitte erneut versuchen.",
|
||||||
"invalidCode": "",
|
"invalidCode": "6-stelligen Code eingeben",
|
||||||
"verifyError": "",
|
"verifyError": "Ungültiger oder abgelaufener Code.",
|
||||||
"back": "",
|
"back": "Zurück",
|
||||||
"title": "",
|
"title": "Ihre Auswahl wiederherstellen",
|
||||||
"emailStepDescription": "",
|
"emailStepDescription": "Geben Sie die E-Mail-Adresse ein, die Sie zuvor verwendet haben. Wir senden Ihnen einen 6-stelligen Bestätigungscode.",
|
||||||
"codeStepDescription": "",
|
"codeStepDescription": "Geben Sie den 6-stelligen Code ein, den wir an Ihre E-Mail-Adresse gesendet haben.",
|
||||||
"emailLabel": "",
|
"emailLabel": "E-Mail",
|
||||||
"sendCode": "",
|
"sendCode": "Code senden",
|
||||||
"codeLabel": "",
|
"codeLabel": "Bestätigungscode",
|
||||||
"verifyCode": ""
|
"verifyCode": "Verifizieren und fortfahren"
|
||||||
},
|
},
|
||||||
"guestPrompt": {
|
"guestPrompt": {
|
||||||
"nameRequired": "",
|
"nameRequired": "Name ist erforderlich",
|
||||||
"invalidEmail": "",
|
"invalidEmail": "Ungültige E-Mail-Adresse",
|
||||||
"emailRequired": "",
|
"emailRequired": "E-Mail ist erforderlich",
|
||||||
"error": "",
|
"error": "Registrierung fehlgeschlagen",
|
||||||
"title": "",
|
"title": "Willkommen — wie heißen Sie?",
|
||||||
"description": "",
|
"description": "Ihre Auswahl wird unter diesem Namen gespeichert, damit der Fotograf weiß, welche Fotos Ihnen gefallen.",
|
||||||
"nameLabel": "",
|
"nameLabel": "Ihr Name",
|
||||||
"namePlaceholder": "",
|
"namePlaceholder": "Name eingeben",
|
||||||
"emailLabelRequired": "",
|
"emailLabelRequired": "E-Mail",
|
||||||
"emailLabel": "",
|
"emailLabel": "E-Mail (optional)",
|
||||||
"emailPlaceholder": "",
|
"emailPlaceholder": "[email protected]",
|
||||||
"submit": "",
|
"submit": "Weiter",
|
||||||
"alreadyHere": ""
|
"alreadyHere": "Ich war schon einmal hier"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"forgetMeConfirm": "",
|
"forgetMeConfirm": "Ihr Name und Ihre Auswahl werden aus dieser Galerie entfernt.",
|
||||||
"forgetMe": ""
|
"forgetMe": "Vergiss mich ({{name}})"
|
||||||
},
|
},
|
||||||
"photosCount_one": "",
|
"photosCount_one": "{{count}} Foto",
|
||||||
"photosCount_other": "",
|
"photosCount_other": "{{count}} Fotos",
|
||||||
"poweredBy": ""
|
"poweredBy": "Bereitgestellt von PicPeak"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"title": "Fotokategorien",
|
"title": "Fotokategorien",
|
||||||
@@ -907,14 +920,14 @@
|
|||||||
"successAll": "{{count}} Veranstaltungen endgültig gelöscht",
|
"successAll": "{{count}} Veranstaltungen endgültig gelöscht",
|
||||||
"successPartial": "{{success}} Veranstaltungen gelöscht, {{failed}} fehlgeschlagen",
|
"successPartial": "{{success}} Veranstaltungen gelöscht, {{failed}} fehlgeschlagen",
|
||||||
"errorGeneric": "Veranstaltungen konnten nicht gelöscht werden",
|
"errorGeneric": "Veranstaltungen konnten nicht gelöscht werden",
|
||||||
"successAll_one": "",
|
"successAll_one": "{{count}} Veranstaltung dauerhaft gelöscht",
|
||||||
"successAll_other": "",
|
"successAll_other": "{{count}} Veranstaltungen dauerhaft gelöscht",
|
||||||
"title_one": "",
|
"title_one": "{{count}} Veranstaltung dauerhaft löschen?",
|
||||||
"title_other": "",
|
"title_other": "{{count}} Veranstaltungen dauerhaft löschen?",
|
||||||
"processing_one": "",
|
"processing_one": "{{count}} Veranstaltung wird gelöscht. Dies kann einige Minuten dauern — bitte schließen Sie dieses Fenster nicht.",
|
||||||
"processing_other": "",
|
"processing_other": "{{count}} Veranstaltungen werden gelöscht. Dies kann einige Minuten dauern — bitte schließen Sie dieses Fenster nicht.",
|
||||||
"submit_one": "",
|
"submit_one": "{{count}} Veranstaltung löschen",
|
||||||
"submit_other": ""
|
"submit_other": "{{count}} Veranstaltungen löschen"
|
||||||
},
|
},
|
||||||
"searchEventsPlaceholder": "Veranstaltungen suchen...",
|
"searchEventsPlaceholder": "Veranstaltungen suchen...",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
@@ -941,21 +954,21 @@
|
|||||||
"complete": "Fertig!",
|
"complete": "Fertig!",
|
||||||
"failed": "Umbenennung fehlgeschlagen",
|
"failed": "Umbenennung fehlgeschlagen",
|
||||||
"confirm": "Veranstaltung umbenennen",
|
"confirm": "Veranstaltung umbenennen",
|
||||||
"success": "",
|
"success": "Veranstaltung erfolgreich umbenannt!",
|
||||||
"filesRenamed_one": "",
|
"filesRenamed_one": "{{count}} Datei aktualisiert",
|
||||||
"filesRenamed_other": "",
|
"filesRenamed_other": "{{count}} Dateien aktualisiert",
|
||||||
"newLink": "",
|
"newLink": "Neuer Galerie-Link",
|
||||||
"currentName": "",
|
"currentName": "Aktueller Name:",
|
||||||
"newName": "",
|
"newName": "Neuer Veranstaltungsname",
|
||||||
"enterNewName": "",
|
"enterNewName": "Neuen Veranstaltungsnamen eingeben",
|
||||||
"newUrl": "",
|
"newUrl": "Neue URL:",
|
||||||
"checkingAvailability": "",
|
"checkingAvailability": "Verfügbarkeit wird geprüft...",
|
||||||
"resendEmail": "",
|
"resendEmail": "Einladungs-E-Mail mit neuem Galerie-Link erneut senden",
|
||||||
"emailTo": "",
|
"emailTo": "Aktualisierte Galerie-Zugangsemail senden an",
|
||||||
"warningTitle": "",
|
"warningTitle": "Bitte beachten:",
|
||||||
"warning1": "",
|
"warning1": "Die Galerie-URL wird sich ändern",
|
||||||
"warning2": "",
|
"warning2": "Alte URLs werden automatisch zur neuen URL weitergeleitet",
|
||||||
"warning3": ""
|
"warning3": "Fotodateien können umbenannt werden"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"totalEvents": "Gesamtveranstaltungen",
|
"totalEvents": "Gesamtveranstaltungen",
|
||||||
@@ -972,27 +985,27 @@
|
|||||||
"downloadingArchive": "{{name}}-Archiv wird heruntergeladen...",
|
"downloadingArchive": "{{name}}-Archiv wird heruntergeladen...",
|
||||||
"downloadStarted": "Download gestartet",
|
"downloadStarted": "Download gestartet",
|
||||||
"failedToDownloadArchive": "Archiv-Download fehlgeschlagen",
|
"failedToDownloadArchive": "Archiv-Download fehlgeschlagen",
|
||||||
"bulkArchiveSuccess_one": "",
|
"bulkArchiveSuccess_one": "{{count}} Veranstaltung erfolgreich archiviert",
|
||||||
"bulkArchiveSuccess_other": "",
|
"bulkArchiveSuccess_other": "{{count}} Veranstaltungen erfolgreich archiviert",
|
||||||
"daysLeft_one": "",
|
"daysLeft_one": "({{count}} Tag verbleibend)",
|
||||||
"daysLeft_other": "",
|
"daysLeft_other": "({{count}} Tage verbleibend)",
|
||||||
"eventsSelected_one": "",
|
"eventsSelected_one": "{{count}} Veranstaltung ausgewählt",
|
||||||
"eventsSelected_other": "",
|
"eventsSelected_other": "{{count}} Veranstaltungen ausgewählt",
|
||||||
"paginationLabel": "",
|
"paginationLabel": "{{from}}–{{to}} von {{total}}",
|
||||||
"filtered": "",
|
"filtered": "gefiltert",
|
||||||
"pageOf": "",
|
"pageOf": "Seite {{page}} von {{totalPages}}",
|
||||||
"notFound": "",
|
"notFound": "Veranstaltung nicht gefunden",
|
||||||
"customerPhone": "",
|
"customerPhone": "Kundentelefon",
|
||||||
"customerPhonePlaceholder": "",
|
"customerPhonePlaceholder": "+49 555 555 1234",
|
||||||
"allowPresignedDownload": "",
|
"allowPresignedDownload": "Direkten S3-Download erlauben (kein Wasserzeichen, nur S3-Modus)",
|
||||||
"neverExpires": "",
|
"neverExpires": "Nie",
|
||||||
"rightClickBlocked": "",
|
"rightClickBlocked": "Rechtsklick gesperrt",
|
||||||
"devtoolsDetection": "",
|
"devtoolsDetection": "DevTools-Erkennung",
|
||||||
"watermarked": "",
|
"watermarked": "Mit Wasserzeichen",
|
||||||
"importExternal": "",
|
"importExternal": "Aus externem Ordner importieren",
|
||||||
"externalImportInfo": "",
|
"externalImportInfo": "Alle Bilder aus dem ausgewählten Ordner werden importiert.",
|
||||||
"selectExternalFolder": "",
|
"selectExternalFolder": "Externen Ordner unter /external-media auswählen",
|
||||||
"importFromSelectedFolder": ""
|
"importFromSelectedFolder": "Aus ausgewähltem Ordner importieren"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Systemeinstellungen",
|
"title": "Systemeinstellungen",
|
||||||
@@ -1139,12 +1152,12 @@
|
|||||||
"saveSettings": "Veranstaltungseinstellungen speichern",
|
"saveSettings": "Veranstaltungseinstellungen speichern",
|
||||||
"noteTitle": "Hinweis",
|
"noteTitle": "Hinweis",
|
||||||
"noteText": "Diese Einstellungen betreffen nur die Erstellung neuer Veranstaltungen. Bestehende Veranstaltungen sind nicht betroffen. Standardmäßig sind alle Felder erforderlich.",
|
"noteText": "Diese Einstellungen betreffen nur die Erstellung neuer Veranstaltungen. Bestehende Veranstaltungen sind nicht betroffen. Standardmäßig sind alle Felder erforderlich.",
|
||||||
"defaultRequirePassword": "",
|
"defaultRequirePassword": "Standardmäßig Passwort erforderlich",
|
||||||
"defaultRequirePasswordHelp": "",
|
"defaultRequirePasswordHelp": "\"Passwort erforderlich\" beim Erstellen neuer Veranstaltungen vorausgewählt. Deaktivieren für schnellere Erstellung öffentlicher Galerien.",
|
||||||
"showGalleryFilterBar": "",
|
"showGalleryFilterBar": "Filterleiste in Galerien anzeigen",
|
||||||
"showGalleryFilterBarHelp": "",
|
"showGalleryFilterBarHelp": "Dateiname-Suche und Sortiersteuerungen über Raster-Galerien anzeigen. Deaktivieren für ein übersichtlicheres Layout.",
|
||||||
"enablePhoneField": "",
|
"enablePhoneField": "Telefonnummer-Feld aktivieren",
|
||||||
"enablePhoneFieldHelp": ""
|
"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": {
|
"imageSecurity": {
|
||||||
"title": "Bildschutz",
|
"title": "Bildschutz",
|
||||||
@@ -1284,8 +1297,8 @@
|
|||||||
"checkSuccess": "Benachrichtigung für neue Version gesendet",
|
"checkSuccess": "Benachrichtigung für neue Version gesendet",
|
||||||
"checkNoAction": "Keine Benachrichtigung erforderlich: {{reason}}",
|
"checkNoAction": "Keine Benachrichtigung erforderlich: {{reason}}",
|
||||||
"checkError": "Fehler beim Prüfen auf Updates",
|
"checkError": "Fehler beim Prüfen auf Updates",
|
||||||
"emailSent_one": "",
|
"emailSent_one": "Benachrichtigungs-E-Mail an {{count}} Empfänger gesendet",
|
||||||
"emailSent_other": ""
|
"emailSent_other": "Benachrichtigungs-E-Mail an {{count}} Empfänger gesendet"
|
||||||
},
|
},
|
||||||
"moderation": {
|
"moderation": {
|
||||||
"title": "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."
|
"privacyText": "IP-Adressen werden aus Datenschutzgründen gehasht. Es werden keine persönlichen Daten gespeichert. Analysedaten werden 90 Tage aufbewahrt."
|
||||||
},
|
},
|
||||||
"groups": {
|
"groups": {
|
||||||
"general": "",
|
"general": "Allgemein",
|
||||||
"display": "",
|
"display": "Anzeige",
|
||||||
"privacySecurity": "",
|
"privacySecurity": "Datenschutz & Sicherheit",
|
||||||
"integrations": "",
|
"integrations": "Integrationen",
|
||||||
"system": ""
|
"system": "System"
|
||||||
},
|
},
|
||||||
"apiTokens": {
|
"apiTokens": {
|
||||||
"title": "",
|
"title": "API-Tokens",
|
||||||
"createError": "",
|
"createError": "Token konnte nicht erstellt werden",
|
||||||
"revoked": "",
|
"revoked": "Token widerrufen",
|
||||||
"subtitle": "",
|
"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": "",
|
"copyNow": "Kopieren Sie diesen Token jetzt — er wird nicht erneut angezeigt.",
|
||||||
"copied": "",
|
"copied": "Kopiert",
|
||||||
"copyFailed": "",
|
"copyFailed": "Kopieren fehlgeschlagen",
|
||||||
"name": "",
|
"name": "Name",
|
||||||
"namePlaceholder": "",
|
"namePlaceholder": "z.B. n8n Produktion",
|
||||||
"scopes": "",
|
"scopes": "Berechtigungen",
|
||||||
"generate": "",
|
"generate": "Token generieren",
|
||||||
"scopeHint": "",
|
"scopeHint": "admin > write > read. Ein reiner Lese-Token kann keine Änderungen vornehmen, auch wenn der Besitzer super_admin ist.",
|
||||||
"existing": "",
|
"existing": "Vorhandene Tokens",
|
||||||
"lastUsed": "",
|
"lastUsed": "Zuletzt verwendet",
|
||||||
"created": "",
|
"created": "Erstellt",
|
||||||
"status": "",
|
"status": "Status",
|
||||||
"statusRevoked": "",
|
"statusRevoked": "Widerrufen",
|
||||||
"statusExpired": "",
|
"statusExpired": "Abgelaufen",
|
||||||
"statusActive": "",
|
"statusActive": "Aktiv",
|
||||||
"confirmRevoke": "",
|
"confirmRevoke": "Diesen Token wirklich widerrufen?",
|
||||||
"revoke": "",
|
"revoke": "Widerrufen",
|
||||||
"empty": ""
|
"empty": "Noch keine Tokens. Generieren Sie oben einen, um zu beginnen."
|
||||||
},
|
},
|
||||||
"webhooks": {
|
"webhooks": {
|
||||||
"title": "",
|
"title": "Webhooks",
|
||||||
"subtitle": "",
|
"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": "",
|
"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": "",
|
"copyNow": "Kopieren Sie dieses Signaturgeheimnis jetzt — es wird nicht erneut angezeigt.",
|
||||||
"name": "",
|
"name": "Name",
|
||||||
"url": "",
|
"url": "Empfänger-URL",
|
||||||
"events": "",
|
"events": "Ereignisse abonnieren",
|
||||||
"filter": "",
|
"filter": "Filter (JSON, optional)",
|
||||||
"template": "",
|
"template": "Vorlage (optional)",
|
||||||
"create": "",
|
"create": "Webhook erstellen",
|
||||||
"existing": "",
|
"existing": "Vorhandene Webhooks",
|
||||||
"empty": ""
|
"empty": "Noch keine Webhooks. Erstellen Sie oben einen, um Ereignisbenachrichtigungen zu erhalten."
|
||||||
},
|
},
|
||||||
"sectionLabel": "",
|
"sectionLabel": "Einstellungsbereich",
|
||||||
"navAriaLabel": ""
|
"navAriaLabel": "Einstellungsnavigation"
|
||||||
},
|
},
|
||||||
"branding": {
|
"branding": {
|
||||||
"title": "Branding & Themen",
|
"title": "Branding & Themen",
|
||||||
@@ -1588,38 +1601,68 @@
|
|||||||
"betaThumbnailWarningTitle": "Niedrige Thumbnail-Auflösung erkannt",
|
"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.",
|
"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",
|
"betaThumbnailWarningLink": "Zu den Thumbnail-Einstellungen",
|
||||||
"logoSize": "",
|
"logoSize": "Logo-Größe",
|
||||||
"surfaceColor": "",
|
"surfaceColor": "Oberfläche",
|
||||||
"elevatedColor": "",
|
"elevatedColor": "Erhöht",
|
||||||
"borderColor": "",
|
"borderColor": "Rahmen",
|
||||||
"mutedTextColor": "",
|
"mutedTextColor": "Gedämpfter Text",
|
||||||
"accentDarkColor": "",
|
"accentDarkColor": "Akzent (gefüllt)",
|
||||||
"syncFromBranding": "",
|
"syncFromBranding": "Vom Branding synchronisieren",
|
||||||
"forceColorMode": "",
|
"forceColorMode": "Farbmodus erzwingen",
|
||||||
"forceColorModeHelp": "",
|
"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": "",
|
"forceColorModeNone": "Kein Zwang (Benutzerauswahl)",
|
||||||
"forceColorModeDark": "",
|
"forceColorModeDark": "Dunkelmodus erzwingen",
|
||||||
"forceColorModeLight": "",
|
"forceColorModeLight": "Hellmodus erzwingen",
|
||||||
"colorGroupSurfaces": "",
|
"colorGroupSurfaces": "Oberflächen",
|
||||||
"colorGroupSurfacesHelp": "",
|
"colorGroupSurfacesHelp": "Die neutralen Ebenen hinter Ihrem Inhalt. Hintergrund liegt am weitesten zurück; Oberfläche und Erhöht stapeln sich darüber.",
|
||||||
"backgroundColorHelp": "",
|
"backgroundColorHelp": "Die Seite selbst — Hintergrundfarbe jeder Galerie, Admin-Seite und CMS-Seite.",
|
||||||
"surfaceColorHelp": "",
|
"surfaceColorHelp": "Karten, Seitenleiste, Kopfzeile und Navigation. Die erste Ebene über dem Hintergrund.",
|
||||||
"elevatedColorHelp": "",
|
"elevatedColorHelp": "Panels, die über Karten schweben: Bild-Platzhalter, Hover-/Aktiv-Zeilen, Modal-Kopfzeilen, Code-Blöcke.",
|
||||||
"borderColorHelp": "",
|
"borderColorHelp": "Trennlinien, Tabellengitterlinien, Kartenumrisse, Eingabe-Rahmen.",
|
||||||
"colorGroupText": "",
|
"colorGroupText": "Text",
|
||||||
"colorGroupTextHelp": "",
|
"colorGroupTextHelp": "Vordergrund-Textfarben. Primär für alles, worauf Leser sich konzentrieren; Sekundär für unterstützenden Text.",
|
||||||
"textColorHelp": "",
|
"textColorHelp": "Überschriften, Fließtext, Tabellenzellen, Formulareingabewerte, Navigationsbezeichnungen — die Haupttextfarbe.",
|
||||||
"mutedTextColorHelp": "",
|
"mutedTextColorHelp": "Bildunterschriften, Hilfetext unter Eingaben, Tabellen-Spaltenüberschriften, Fußzeilenlinks, Daten und Metadaten.",
|
||||||
"colorGroupAccent": "",
|
"colorGroupAccent": "Akzent",
|
||||||
"colorGroupAccentHelp": "",
|
"colorGroupAccentHelp": "Markenfarben, die interaktive Elemente hervorheben. Verwenden Sie ein starkes Farbpaar — Akzent für Umrisse/Text, Akzent Dunkel für gefüllte Buttons.",
|
||||||
"accentColorHelp": "",
|
"accentColorHelp": "Links, Symbole, Fokusringe, Hover-Zustände bei Primär-Buttons, aktive Seitenleisten-Unterstreichung. Sollte auf Hintergrund und Oberfläche gut lesbar sein.",
|
||||||
"accentDarkColorHelp": "",
|
"accentDarkColorHelp": "Gefüllte CTA-Buttons, aktiver Seitenleisten-Eintrag-Hintergrund, Abzeichen und Tags. Muss genug Kontrast für weißen Text bieten.",
|
||||||
"cssTemplate": "",
|
"cssTemplate": "CSS-Vorlage",
|
||||||
"cssTemplateDescription": "",
|
"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": "",
|
"noTemplate": "Keine Vorlage",
|
||||||
"noTemplateDescription": "",
|
"noTemplateDescription": "Nur Theme-Einstellungen ohne CSS-Vorlage verwenden",
|
||||||
"templateSlot": "",
|
"templateSlot": "Slot {{slot}}",
|
||||||
"eventCustomCSS": ""
|
"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": {
|
"admin": {
|
||||||
"title": "Admin-Panel",
|
"title": "Admin-Panel",
|
||||||
@@ -1635,7 +1678,10 @@
|
|||||||
"archivedEvents": "Archivierte Veranstaltungen",
|
"archivedEvents": "Archivierte Veranstaltungen",
|
||||||
"systemHealth": "Systemstatus",
|
"systemHealth": "Systemstatus",
|
||||||
"health": {
|
"health": {
|
||||||
"checking": "Prüfe..."
|
"checking": "Prüfe...",
|
||||||
|
"healthy": "Gesund",
|
||||||
|
"warning": "Warnung",
|
||||||
|
"error": "Fehler"
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"available": "Update verfügbar",
|
"available": "Update verfügbar",
|
||||||
@@ -1694,17 +1740,17 @@
|
|||||||
"archiveRestored": "Archiv für \"{{eventName}}\" wiederhergestellt",
|
"archiveRestored": "Archiv für \"{{eventName}}\" wiederhergestellt",
|
||||||
"systemActivity": "Systemaktivität: {{type}}",
|
"systemActivity": "Systemaktivität: {{type}}",
|
||||||
"adminProfileUpdated": "Admin-Profil aktualisiert von {{actorName}}",
|
"adminProfileUpdated": "Admin-Profil aktualisiert von {{actorName}}",
|
||||||
"photosUploaded_one": "",
|
"photosUploaded_one": "{{count}} Foto in \"{{eventName}}\" hochgeladen",
|
||||||
"photosUploaded_other": "",
|
"photosUploaded_other": "{{count}} Fotos in \"{{eventName}}\" hochgeladen",
|
||||||
"photosBulkDeleted_one": "",
|
"photosBulkDeleted_one": "{{count}} Foto aus \"{{eventName}}\" gelöscht",
|
||||||
"photosBulkDeleted_other": "",
|
"photosBulkDeleted_other": "{{count}} Fotos aus \"{{eventName}}\" gelöscht",
|
||||||
"bulkDownload_one": "",
|
"bulkDownload_one": "{{count}} Foto aus \"{{eventName}}\" heruntergeladen",
|
||||||
"bulkDownload_other": ""
|
"bulkDownload_other": "{{count}} Fotos aus \"{{eventName}}\" heruntergeladen"
|
||||||
},
|
},
|
||||||
"notificationToasts": {
|
"notificationToasts": {
|
||||||
"markedAllRead": "Alle Benachrichtigungen als gelesen markiert",
|
"markedAllRead": "Alle Benachrichtigungen als gelesen markiert",
|
||||||
"clearedAll_one": "",
|
"clearedAll_one": "{{count}} Benachrichtigung gelöscht",
|
||||||
"clearedAll_other": ""
|
"clearedAll_other": "{{count}} Benachrichtigungen gelöscht"
|
||||||
},
|
},
|
||||||
"changePassword": "Passwort ändern",
|
"changePassword": "Passwort ändern",
|
||||||
"loadingDashboard": "Dashboard wird geladen...",
|
"loadingDashboard": "Dashboard wird geladen...",
|
||||||
@@ -1724,73 +1770,135 @@
|
|||||||
"showSelected": "Einblenden",
|
"showSelected": "Einblenden",
|
||||||
"hiddenSuccess": "Fotos für Gäste ausgeblendet",
|
"hiddenSuccess": "Fotos für Gäste ausgeblendet",
|
||||||
"visibleSuccess": "Fotos jetzt für Gäste sichtbar",
|
"visibleSuccess": "Fotos jetzt für Gäste sichtbar",
|
||||||
"processingStatus": "",
|
"processingStatus": "Wird verarbeitet…",
|
||||||
"processingFailed": "",
|
"processingFailed": "Fehlgeschlagen",
|
||||||
"retryQueued": ""
|
"retryQueued": "Wiederholung in Warteschlange"
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"guests": ""
|
"guests": "Gäste"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"daysLeft_one": "",
|
"daysLeft_one": "{{count}} Tag verbleibend",
|
||||||
"daysLeft_other": "",
|
"daysLeft_other": "{{count}} Tage verbleibend",
|
||||||
"viewAllExpiringEvents_one": "",
|
"viewAllExpiringEvents_one": "Alle {{count}} ablaufenden Veranstaltungen anzeigen",
|
||||||
"viewAllExpiringEvents_other": "",
|
"viewAllExpiringEvents_other": "Alle {{count}} ablaufenden Veranstaltungen anzeigen",
|
||||||
"guests": {
|
"guests": {
|
||||||
"loading": "",
|
"loading": "Wird geladen...",
|
||||||
"aggregate": {
|
"aggregate": {
|
||||||
"empty": "",
|
"empty": "Noch keine Gastauswahl.",
|
||||||
"description": ""
|
"description": "Fotos sortiert nach der Anzahl der Gäste, die sie geliket oder favorisiert haben."
|
||||||
},
|
},
|
||||||
"inviteCreated": "",
|
"inviteCreated": "Einladung erstellt",
|
||||||
"inviteCreateError": "",
|
"inviteCreateError": "Einladung konnte nicht erstellt werden",
|
||||||
"inviteRevoked": "",
|
"inviteRevoked": "Einladung widerrufen",
|
||||||
"inviteRevokeError": "",
|
"inviteRevokeError": "Einladung konnte nicht widerrufen werden",
|
||||||
"invitesTitle": "",
|
"invitesTitle": "Gasteinladungen",
|
||||||
"createInvite": "",
|
"createInvite": "Einladung erstellen",
|
||||||
"inviteName": "",
|
"inviteName": "Gastname",
|
||||||
"inviteEmail": "",
|
"inviteEmail": "E-Mail (optional)",
|
||||||
"generateInvite": "",
|
"generateInvite": "Einladungslink generieren",
|
||||||
"existingInvites": "",
|
"existingInvites": "Vorhandene Einladungen",
|
||||||
"noInvites": "",
|
"noInvites": "Noch keine Einladungen",
|
||||||
"copyLink": "",
|
"copyLink": "Link kopieren",
|
||||||
"revokeInvite": "",
|
"revokeInvite": "Widerrufen",
|
||||||
"deletedToast": "",
|
"deletedToast": "Gast entfernt",
|
||||||
"deletedError": "",
|
"deletedError": "Gast konnte nicht entfernt werden",
|
||||||
"mergedToast": "",
|
"mergedToast": "Gäste zusammengeführt",
|
||||||
"mergedError": "",
|
"mergedError": "Gäste konnten nicht zusammengeführt werden",
|
||||||
"forgetGuestConfirm": "",
|
"forgetGuestConfirm": "Diesen Gast entfernen? Ihre Auswahl wird anonymisiert, aber in den Gesamtzahlen behalten.",
|
||||||
"exportError": "",
|
"exportError": "Export fehlgeschlagen",
|
||||||
"mergeSelectAtLeastTwo": "",
|
"mergeSelectAtLeastTwo": "Mindestens 2 Gäste zum Zusammenführen auswählen",
|
||||||
"mergeConfirm_one": "",
|
"mergeConfirm_one": "{{count}} Gast in {{name}} zusammenführen? Dies kann nicht rückgängig gemacht werden.",
|
||||||
"mergeConfirm_other": "",
|
"mergeConfirm_other": "{{count}} Gäste in {{name}} zusammenführen? Dies kann nicht rückgängig gemacht werden.",
|
||||||
"backToList": "",
|
"backToList": "Zurück zur Liste",
|
||||||
"title": "",
|
"title": "Gäste",
|
||||||
"mergeSelected_one": "",
|
"mergeSelected_one": "{{count}} ausgewählt",
|
||||||
"mergeSelected_other": "",
|
"mergeSelected_other": "{{count}} ausgewählt",
|
||||||
"mergeNow": "",
|
"mergeNow": "Auswahl zusammenführen",
|
||||||
"aggregateView": "",
|
"aggregateView": "Nach Beliebtheit",
|
||||||
"mergeMode": "",
|
"mergeMode": "Zusammenführen",
|
||||||
"exportAll": "",
|
"exportAll": "Alle exportieren",
|
||||||
"empty": "",
|
"empty": "Noch keine Gäste registriert.",
|
||||||
"columns": {
|
"columns": {
|
||||||
"name": "",
|
"name": "Name",
|
||||||
"email": "",
|
"email": "E-Mail",
|
||||||
"likes": "",
|
"likes": "Likes",
|
||||||
"favorites": "",
|
"favorites": "Favoriten",
|
||||||
"comments": "",
|
"comments": "Kommentare",
|
||||||
"ratings": "",
|
"ratings": "Bewertungen",
|
||||||
"lastSeen": ""
|
"lastSeen": "Zuletzt gesehen"
|
||||||
},
|
},
|
||||||
"view": "",
|
"view": "Details anzeigen",
|
||||||
"export": "",
|
"export": "Exportieren",
|
||||||
"forgetGuest": "",
|
"forgetGuest": "Gast entfernen",
|
||||||
"loadingDetail": "",
|
"loadingDetail": "Auswahl wird geladen...",
|
||||||
"detail": {
|
"detail": {
|
||||||
"noComments": "",
|
"noComments": "Keine Kommentare",
|
||||||
"empty": ""
|
"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": {
|
"acceptInvitation": {
|
||||||
@@ -1857,8 +1965,8 @@
|
|||||||
"requiredFields": "Bitte füllen Sie alle Pflichtfelder aus",
|
"requiredFields": "Bitte füllen Sie alle Pflichtfelder aus",
|
||||||
"enterTestEmail": "Bitte geben Sie eine Test-E-Mail-Adresse ein",
|
"enterTestEmail": "Bitte geben Sie eine Test-E-Mail-Adresse ein",
|
||||||
"eventCreationFailed": "Veranstaltung konnte nicht erstellt werden",
|
"eventCreationFailed": "Veranstaltung konnte nicht erstellt werden",
|
||||||
"noShareLink": "",
|
"noShareLink": "Kein Freigabe-Link verfügbar",
|
||||||
"copyFailed": ""
|
"copyFailed": "Link konnte nicht kopiert werden"
|
||||||
},
|
},
|
||||||
"legal": {
|
"legal": {
|
||||||
"impressum": "Impressum",
|
"impressum": "Impressum",
|
||||||
@@ -1880,8 +1988,8 @@
|
|||||||
"brandingUpdated": "Branding erfolgreich aktualisiert",
|
"brandingUpdated": "Branding erfolgreich aktualisiert",
|
||||||
"categoryUpdated": "Kategorie erfolgreich aktualisiert",
|
"categoryUpdated": "Kategorie erfolgreich aktualisiert",
|
||||||
"emailConfigSaved": "E-Mail-Konfiguration erfolgreich gespeichert",
|
"emailConfigSaved": "E-Mail-Konfiguration erfolgreich gespeichert",
|
||||||
"brandingThemeMissing": "",
|
"brandingThemeMissing": "Es wurde noch kein Branding-Theme gespeichert.",
|
||||||
"brandingPaletteSynced": ""
|
"brandingPaletteSynced": "Palette vom Branding synchronisiert."
|
||||||
},
|
},
|
||||||
"analytics": {
|
"analytics": {
|
||||||
"title": "Analytics Dashboard",
|
"title": "Analytics Dashboard",
|
||||||
@@ -1978,22 +2086,22 @@
|
|||||||
"noTranslation": "Noch keine Übersetzung",
|
"noTranslation": "Noch keine Übersetzung",
|
||||||
"noTranslationYet": "Für diese Sprache existiert noch keine Übersetzung. Kopieren Sie von einer vorhandenen Sprache:",
|
"noTranslationYet": "Für diese Sprache existiert noch keine Übersetzung. Kopieren Sie von einer vorhandenen Sprache:",
|
||||||
"copyFrom": "Kopieren von",
|
"copyFrom": "Kopieren von",
|
||||||
"syncedFromBranding": "",
|
"syncedFromBranding": "E-Mail-Farben vom Branding synchronisiert. Klicken Sie auf Speichern, um sie anzuwenden.",
|
||||||
"syncFromBranding": "",
|
"syncFromBranding": "Vom Branding synchronisieren",
|
||||||
"primaryColorHelp": "",
|
"primaryColorHelp": "Kopfzeile, H2-Überschriften, Button-Hintergrund, Linkfarbe. Entspricht Branding → Akzent (gefüllt).",
|
||||||
"secondaryColorHelp": "",
|
"secondaryColorHelp": "Fußzeilen-Hintergrund. Entspricht Branding → Oberfläche.",
|
||||||
"bodyBgColor": "",
|
"bodyBgColor": "Seitenhintergrund",
|
||||||
"bodyBgColorHelp": "",
|
"bodyBgColorHelp": "Der Rahmen um die E-Mail-Karte — was der Empfänger hinter der E-Mail sieht. Entspricht Branding → Hintergrund.",
|
||||||
"containerBgColor": "",
|
"containerBgColor": "E-Mail-Karte",
|
||||||
"containerBgColorHelp": "",
|
"containerBgColorHelp": "Die weiße Karte, die den E-Mail-Inhalt enthält. Entspricht Branding → Oberfläche.",
|
||||||
"listBgColor": "",
|
"listBgColor": "Infopanel",
|
||||||
"listBgColorHelp": "",
|
"listBgColorHelp": "Hintergrund der Aufzählungs-Infopanels im E-Mail-Text. Entspricht Branding → Erhöht.",
|
||||||
"bodyTextColor": "",
|
"bodyTextColor": "Fließtext",
|
||||||
"bodyTextColorHelp": "",
|
"bodyTextColorHelp": "Absatz- und Fetttext-Farbe. Entspricht Branding → Primärer Text.",
|
||||||
"mutedTextColor": "",
|
"mutedTextColor": "Fußzeilentext",
|
||||||
"mutedTextColorHelp": "",
|
"mutedTextColorHelp": "Fußzeilentext und Copyright-Zeile. Entspricht Branding → Sekundärer Text.",
|
||||||
"buttonTextColor": "",
|
"buttonTextColor": "Button-Text",
|
||||||
"buttonTextColorHelp": ""
|
"buttonTextColorHelp": "Textfarbe auf gefüllten Buttons. Sollte klar gegen die Primärfarbe kontrastieren. Kein Branding-Äquivalent — meistens weiß."
|
||||||
},
|
},
|
||||||
"cms": {
|
"cms": {
|
||||||
"title": "CMS-Seiten",
|
"title": "CMS-Seiten",
|
||||||
@@ -2015,14 +2123,14 @@
|
|||||||
"externalUrlPlaceholder": "https://example.com/impressum",
|
"externalUrlPlaceholder": "https://example.com/impressum",
|
||||||
"externalUrlInvalid": "Muss eine gültige https://-URL sein",
|
"externalUrlInvalid": "Muss eine gültige https://-URL sein",
|
||||||
"externalUrlActive": "Externe URL ist aktiv — interne Inhalte bleiben gespeichert, werden Besuchern aber nicht angezeigt.",
|
"externalUrlActive": "Externe URL ist aktiv — interne Inhalte bleiben gespeichert, werden Besuchern aber nicht angezeigt.",
|
||||||
"logoUploaded": "",
|
"logoUploaded": "Logo hochgeladen",
|
||||||
"logoCleared": "",
|
"logoCleared": "Logo entfernt",
|
||||||
"pageLogo": "",
|
"pageLogo": "Seitenlogo",
|
||||||
"pageLogoHelp": "",
|
"pageLogoHelp": "Optional. Falls gesetzt, wird es anstelle des globalen Branding-Logos auf dieser Seite verwendet.",
|
||||||
"noLogo": "",
|
"noLogo": "keine Überschreibung",
|
||||||
"replaceLogo": "",
|
"replaceLogo": "Logo ersetzen",
|
||||||
"uploadLogo": "",
|
"uploadLogo": "Logo hochladen",
|
||||||
"clearLogo": ""
|
"clearLogo": "Seitenstandard verwenden"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"eventNameRequired": "Veranstaltungsname ist erforderlich",
|
"eventNameRequired": "Veranstaltungsname ist erforderlich",
|
||||||
@@ -2034,10 +2142,10 @@
|
|||||||
"passwordMinLength": "Passwort muss mindestens 6 Zeichen lang sein",
|
"passwordMinLength": "Passwort muss mindestens 6 Zeichen lang sein",
|
||||||
"passwordsDoNotMatch": "Passwörter stimmen nicht überein",
|
"passwordsDoNotMatch": "Passwörter stimmen nicht überein",
|
||||||
"expirationRange": "Ablauf muss zwischen 1 und 365 Tagen liegen",
|
"expirationRange": "Ablauf muss zwischen 1 und 365 Tagen liegen",
|
||||||
"required": "",
|
"required": "Dieses Feld ist erforderlich",
|
||||||
"expirationRequired": "",
|
"expirationRequired": "Ablaufdatum ist erforderlich.",
|
||||||
"eventDateRequired": "",
|
"eventDateRequired": "Veranstaltungsdatum ist erforderlich",
|
||||||
"passwordTooSimple": ""
|
"passwordTooSimple": "Das Passwort darf nicht nur aus Zahlen bestehen. Verwenden Sie z.B. ein Datumsformat wie \"04.07.2025\""
|
||||||
},
|
},
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"title": "Systemwartung",
|
"title": "Systemwartung",
|
||||||
@@ -2133,96 +2241,96 @@
|
|||||||
"commentsDesc": "Textkommentare auf Fotos",
|
"commentsDesc": "Textkommentare auf Fotos",
|
||||||
"favorites": "Favoriten",
|
"favorites": "Favoriten",
|
||||||
"favoritesDesc": "Fotos als Favoriten markieren",
|
"favoritesDesc": "Fotos als Favoriten markieren",
|
||||||
"identityMode": "",
|
"identityMode": "Identitätsmodus",
|
||||||
"identityModeSimple": "",
|
"identityModeSimple": "Einfaches Feedback",
|
||||||
"identityModeSimpleDesc": "",
|
"identityModeSimpleDesc": "Anonym, gerätebasiert. Alle Besucher auf demselben Gerät teilen den Zustand.",
|
||||||
"identityModeGuest": "",
|
"identityModeGuest": "Gastbezogene Auswahl",
|
||||||
"identityModeGuestDesc": "",
|
"identityModeGuestDesc": "Jeder Besucher gibt seinen Namen ein. Ermöglicht gastbezogenes Tracking und Admin-Einblicke.",
|
||||||
"privacyModeration": "",
|
"privacyModeration": "Datenschutz & Moderation",
|
||||||
"requireInfo": "",
|
"requireInfo": "Name & E-Mail erforderlich",
|
||||||
"requireInfoDesc": "",
|
"requireInfoDesc": "Gäste müssen Name und E-Mail angeben, um Feedback zu hinterlassen",
|
||||||
"moderateComments": "",
|
"moderateComments": "Kommentare moderieren",
|
||||||
"moderateCommentsDesc": "",
|
"moderateCommentsDesc": "Kommentare benötigen eine Genehmigung, bevor sie sichtbar sind",
|
||||||
"showToGuests": "",
|
"showToGuests": "Feedback für Gäste anzeigen",
|
||||||
"showToGuestsDesc": "",
|
"showToGuestsDesc": "Andere Gäste können Bewertungen, Likes und genehmigte Kommentare sehen",
|
||||||
"enableRateLimiting": "",
|
"enableRateLimiting": "Ratenbegrenzung aktivieren",
|
||||||
"rateLimitingDesc": "",
|
"rateLimitingDesc": "Spam durch Begrenzung der Feedback-Häufigkeit verhindern",
|
||||||
"timeWindow": "",
|
"timeWindow": "Zeitfenster (Minuten)",
|
||||||
"maxRequests": ""
|
"maxRequests": "Maximale Anfragen"
|
||||||
},
|
},
|
||||||
"settingsUpdated": "",
|
"settingsUpdated": "Feedback-Einstellungen aktualisiert",
|
||||||
"settingsUpdateError": "",
|
"settingsUpdateError": "Einstellungen konnten nicht aktualisiert werden",
|
||||||
"moderated": "",
|
"moderated": "Feedback moderiert",
|
||||||
"deleted": "",
|
"deleted": "Feedback gelöscht",
|
||||||
"exported": "",
|
"exported": "Feedback exportiert",
|
||||||
"exportError": "",
|
"exportError": "Feedback konnte nicht exportiert werden",
|
||||||
"title": "",
|
"title": "Feedback-Verwaltung",
|
||||||
"exportCSV": "",
|
"exportCSV": "CSV exportieren",
|
||||||
"exportJSON": "",
|
"exportJSON": "JSON exportieren",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"settings": "",
|
"settings": "Einstellungen",
|
||||||
"feedback": "",
|
"feedback": "Feedback",
|
||||||
"analytics": "",
|
"analytics": "Analytik",
|
||||||
"moderation": ""
|
"moderation": "Moderation"
|
||||||
},
|
},
|
||||||
"allTypes": "",
|
"allTypes": "Alle Typen",
|
||||||
"types": {
|
"types": {
|
||||||
"rating": "",
|
"rating": "Bewertungen",
|
||||||
"like": "",
|
"like": "Likes",
|
||||||
"comment": "",
|
"comment": "Kommentare",
|
||||||
"favorite": ""
|
"favorite": "Favoriten"
|
||||||
},
|
},
|
||||||
"allStatuses": "",
|
"allStatuses": "Alle Status",
|
||||||
"status": {
|
"status": {
|
||||||
"pending": "",
|
"pending": "Ausstehend",
|
||||||
"approved": "",
|
"approved": "Genehmigt",
|
||||||
"hidden": ""
|
"hidden": "Versteckt"
|
||||||
},
|
},
|
||||||
"noFeedback": "",
|
"noFeedback": "Kein Feedback gefunden",
|
||||||
"approve": "",
|
"approve": "Genehmigen",
|
||||||
"hide": "",
|
"hide": "Ausblenden",
|
||||||
"unhide": "",
|
"unhide": "Einblenden",
|
||||||
"confirmDelete": "",
|
"confirmDelete": "Dieses Feedback wirklich löschen?",
|
||||||
"avgRating": "",
|
"avgRating": "Durchschnittsbewertung",
|
||||||
"totalRatings_one": "",
|
"totalRatings_one": "{{count}} Bewertungen",
|
||||||
"totalRatings_other": "",
|
"totalRatings_other": "{{count}} Bewertungen",
|
||||||
"totalLikes": "",
|
"totalLikes": "Gesamte Likes",
|
||||||
"totalComments": "",
|
"totalComments": "Gesamte Kommentare",
|
||||||
"pendingModeration_one": "",
|
"pendingModeration_one": "{{count}} ausstehend",
|
||||||
"pendingModeration_other": "",
|
"pendingModeration_other": "{{count}} ausstehend",
|
||||||
"totalInteractions": "",
|
"totalInteractions": "Gesamte Interaktionen",
|
||||||
"topRated": "",
|
"topRated": "Bestbewertete Fotos",
|
||||||
"recentComments": "",
|
"recentComments": "Neueste Kommentare",
|
||||||
"wordFilters": "",
|
"wordFilters": "Wortfilter",
|
||||||
"wordFiltersDesc": "",
|
"wordFiltersDesc": "Gesperrte Wörter für die Kommentarmoderation verwalten",
|
||||||
"manageFilters": "",
|
"manageFilters": "Wortfilter verwalten",
|
||||||
"manage": "",
|
"manage": "Feedback verwalten",
|
||||||
"ratingSubmitted": "",
|
"ratingSubmitted": "Bewertung abgegeben",
|
||||||
"ratingError": "",
|
"ratingError": "Bewertung konnte nicht abgegeben werden",
|
||||||
"rateStar_one": "",
|
"rateStar_one": "{{count}} Stern bewerten",
|
||||||
"rateStar_other": "",
|
"rateStar_other": "{{count}} Sterne bewerten",
|
||||||
"ratingsCount_one": "",
|
"ratingsCount_one": "{{count}} Bewertungen",
|
||||||
"ratingsCount_other": "",
|
"ratingsCount_other": "{{count}} Bewertungen",
|
||||||
"likeError": "",
|
"likeError": "Like konnte nicht aktualisiert werden",
|
||||||
"unlike": "",
|
"unlike": "Nicht mehr mögen",
|
||||||
"like": "",
|
"like": "Gefällt mir",
|
||||||
"favoriteError": "",
|
"favoriteError": "Favorit konnte nicht aktualisiert werden",
|
||||||
"unfavorite": "",
|
"unfavorite": "Aus Favoriten entfernen",
|
||||||
"favorite": "",
|
"favorite": "Zu Favoriten hinzufügen",
|
||||||
"invalidEmail": "",
|
"invalidEmail": "Ungültige E-Mail-Adresse",
|
||||||
"identityRequired": "",
|
"identityRequired": "Ihre Informationen erforderlich",
|
||||||
"identityReason": "",
|
"identityReason": "Bitte geben Sie Ihren Namen und Ihre E-Mail an, um {{type}} zu senden.",
|
||||||
"namePlaceholder": "",
|
"namePlaceholder": "Ihren Namen eingeben",
|
||||||
"emailPlaceholder": "",
|
"emailPlaceholder": "Ihre E-Mail eingeben",
|
||||||
"submitFeedback": "",
|
"submitFeedback": "Feedback senden",
|
||||||
"moderationSuccess": "",
|
"moderationSuccess": "Feedback moderiert",
|
||||||
"pendingModeration": "",
|
"pendingModeration": "Ausstehende Moderation",
|
||||||
"pending": "",
|
"pending": "ausstehend",
|
||||||
"noPendingComments": "",
|
"noPendingComments": "Keine Kommentare warten auf Moderation",
|
||||||
"onPhoto": "",
|
"onPhoto": "Auf Foto",
|
||||||
"showAll_one": "",
|
"showAll_one": "Alle {{count}} ausstehenden Kommentare anzeigen",
|
||||||
"showAll_other": "",
|
"showAll_other": "Alle {{count}} ausstehenden Kommentare anzeigen",
|
||||||
"viewAllFeedback": ""
|
"viewAllFeedback": "Gesamtes Feedback & Einstellungen anzeigen"
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"feedbackFilters": "Feedback-Filter",
|
"feedbackFilters": "Feedback-Filter",
|
||||||
@@ -2233,7 +2341,14 @@
|
|||||||
"hasComments": "Hat Kommentare",
|
"hasComments": "Hat Kommentare",
|
||||||
"showingPhotos": "Fotos gesamt",
|
"showingPhotos": "Fotos gesamt",
|
||||||
"withRatings": "Mit Bewertungen",
|
"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": {
|
"export": {
|
||||||
"button": "Exportieren",
|
"button": "Exportieren",
|
||||||
@@ -2241,8 +2356,8 @@
|
|||||||
"error": "Export fehlgeschlagen: ",
|
"error": "Export fehlgeschlagen: ",
|
||||||
"exportFiltered": "Gefilterte Fotos exportieren",
|
"exportFiltered": "Gefilterte Fotos exportieren",
|
||||||
"hint": "Fotos auswählen oder Filter anwenden zum Exportieren",
|
"hint": "Fotos auswählen oder Filter anwenden zum Exportieren",
|
||||||
"exportSelected_one": "",
|
"exportSelected_one": "{{count}} ausgewähltes exportieren",
|
||||||
"exportSelected_other": ""
|
"exportSelected_other": "{{count}} ausgewählte exportieren"
|
||||||
},
|
},
|
||||||
"clientAccess": {
|
"clientAccess": {
|
||||||
"title": "Kundenzugang",
|
"title": "Kundenzugang",
|
||||||
@@ -2324,15 +2439,15 @@
|
|||||||
"dateTaken": "Aufnahmedatum"
|
"dateTaken": "Aufnahmedatum"
|
||||||
},
|
},
|
||||||
"photos": {
|
"photos": {
|
||||||
"moveToCategory_one": "",
|
"moveToCategory_one": "{{count}} Foto in Kategorie verschieben",
|
||||||
"moveToCategory_other": "",
|
"moveToCategory_other": "{{count}} Fotos in Kategorie verschieben",
|
||||||
"selectCategory": "",
|
"selectCategory": "Kategorie auswählen",
|
||||||
"uncategorized": "",
|
"uncategorized": "Ohne Kategorie",
|
||||||
"movePhotos": "",
|
"movePhotos": "Fotos verschieben",
|
||||||
"selectedCategory": "",
|
"selectedCategory": "Ausgewählte Kategorie",
|
||||||
"movedToCategory_one": "",
|
"movedToCategory_one": "{{count}} Foto nach {{category}} verschoben",
|
||||||
"movedToCategory_other": "",
|
"movedToCategory_other": "{{count}} Fotos nach {{category}} verschoben",
|
||||||
"moveToCategoryFailed": "",
|
"moveToCategoryFailed": "Fotos konnten nicht in die Kategorie verschoben werden",
|
||||||
"moveToCategory": ""
|
"moveToCategory": "In Kategorie verschieben"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,14 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"events": "Events",
|
"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": {
|
"archives": {
|
||||||
"title": "Archives",
|
"title": "Archives",
|
||||||
@@ -1295,7 +1302,37 @@
|
|||||||
"noTemplate": "No Template",
|
"noTemplate": "No Template",
|
||||||
"noTemplateDescription": "Use only theme settings without a CSS template",
|
"noTemplateDescription": "Use only theme settings without a CSS template",
|
||||||
"templateSlot": "Slot {{slot}}",
|
"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": {
|
"admin": {
|
||||||
"title": "Admin Panel",
|
"title": "Admin Panel",
|
||||||
@@ -1309,6 +1346,9 @@
|
|||||||
"archivedEvents": "Archived Events",
|
"archivedEvents": "Archived Events",
|
||||||
"systemHealth": "System Health",
|
"systemHealth": "System Health",
|
||||||
"health": {
|
"health": {
|
||||||
|
"healthy": "Healthy",
|
||||||
|
"warning": "Warning",
|
||||||
|
"error": "Error",
|
||||||
"checking": "Checking..."
|
"checking": "Checking..."
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
@@ -1465,8 +1505,70 @@
|
|||||||
"loadingDetail": "Loading selections...",
|
"loadingDetail": "Loading selections...",
|
||||||
"detail": {
|
"detail": {
|
||||||
"noComments": "No comments",
|
"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": {
|
"acceptInvitation": {
|
||||||
@@ -1799,7 +1901,13 @@
|
|||||||
},
|
},
|
||||||
"storageDestination": "Storage Destination",
|
"storageDestination": "Storage Destination",
|
||||||
"backupType": "{{type}} backup",
|
"backupType": "{{type}} backup",
|
||||||
"noDestinationSet": "No destination set"
|
"noDestinationSet": "No destination set",
|
||||||
|
"healthStatus": {
|
||||||
|
"excellent": "Excellent",
|
||||||
|
"good": "Good",
|
||||||
|
"warning": "Warning",
|
||||||
|
"critical": "Critical"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"enableBackup": "Enable Automated Backups",
|
"enableBackup": "Enable Automated Backups",
|
||||||
@@ -2254,7 +2362,14 @@
|
|||||||
"hasComments": "Has comments",
|
"hasComments": "Has comments",
|
||||||
"showingPhotos": "Total photos",
|
"showingPhotos": "Total photos",
|
||||||
"withRatings": "With ratings",
|
"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": {
|
"adminLogin": {
|
||||||
"title": "Admin Login",
|
"title": "Admin Login",
|
||||||
|
|||||||
@@ -161,7 +161,14 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboard": "Tableau de bord",
|
"dashboard": "Tableau de bord",
|
||||||
"events": "Événements",
|
"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": {
|
"archives": {
|
||||||
"title": "Archives",
|
"title": "Archives",
|
||||||
@@ -1307,7 +1314,37 @@
|
|||||||
"noTemplate": "Aucun modèle",
|
"noTemplate": "Aucun modèle",
|
||||||
"noTemplateDescription": "Utiliser uniquement les paramètres de thème sans modèle CSS",
|
"noTemplateDescription": "Utiliser uniquement les paramètres de thème sans modèle CSS",
|
||||||
"templateSlot": "Emplacement {{slot}}",
|
"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": {
|
"admin": {
|
||||||
"title": "Panneau d'administration",
|
"title": "Panneau d'administration",
|
||||||
@@ -1321,7 +1358,10 @@
|
|||||||
"archivedEvents": "Événements archivés",
|
"archivedEvents": "Événements archivés",
|
||||||
"systemHealth": "Santé système",
|
"systemHealth": "Santé système",
|
||||||
"health": {
|
"health": {
|
||||||
"checking": "Vérification..."
|
"checking": "Vérification...",
|
||||||
|
"healthy": "Sain",
|
||||||
|
"warning": "Avertissement",
|
||||||
|
"error": "Erreur"
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"available": "Mise à jour disponible",
|
"available": "Mise à jour disponible",
|
||||||
@@ -1475,7 +1515,17 @@
|
|||||||
"loadingDetail": "Chargement des sélections…",
|
"loadingDetail": "Chargement des sélections…",
|
||||||
"detail": {
|
"detail": {
|
||||||
"noComments": "Aucun commentaire",
|
"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": {
|
"photos": {
|
||||||
@@ -1487,6 +1537,58 @@
|
|||||||
"processingStatus": "En cours…",
|
"processingStatus": "En cours…",
|
||||||
"processingFailed": "Échoué",
|
"processingFailed": "Échoué",
|
||||||
"retryQueued": "Nouvelle tentative en file d'attente"
|
"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": {
|
"acceptInvitation": {
|
||||||
@@ -1819,7 +1921,13 @@
|
|||||||
},
|
},
|
||||||
"storageDestination": "Destination",
|
"storageDestination": "Destination",
|
||||||
"backupType": "Sauvegarde {{type}}",
|
"backupType": "Sauvegarde {{type}}",
|
||||||
"noDestinationSet": "Pas de destination"
|
"noDestinationSet": "Pas de destination",
|
||||||
|
"healthStatus": {
|
||||||
|
"warning": "Avertissement",
|
||||||
|
"excellent": "Excellent",
|
||||||
|
"good": "Bon",
|
||||||
|
"critical": "Critique"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"enableBackup": "Activer les sauvegardes auto",
|
"enableBackup": "Activer les sauvegardes auto",
|
||||||
@@ -2279,7 +2387,14 @@
|
|||||||
"hasComments": "Avec Commentaires",
|
"hasComments": "Avec Commentaires",
|
||||||
"combineWith": "Combiner avec",
|
"combineWith": "Combiner avec",
|
||||||
"showingPhotos": "Total photos",
|
"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": {
|
"adminLogin": {
|
||||||
"title": "Connexion Admin",
|
"title": "Connexion Admin",
|
||||||
|
|||||||
@@ -158,7 +158,14 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"events": "Evenementen",
|
"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": {
|
"archives": {
|
||||||
"title": "Archieven",
|
"title": "Archieven",
|
||||||
@@ -1295,7 +1302,37 @@
|
|||||||
"noTemplate": "Geen sjabloon",
|
"noTemplate": "Geen sjabloon",
|
||||||
"noTemplateDescription": "Alleen thema-instellingen gebruiken zonder CSS-sjabloon",
|
"noTemplateDescription": "Alleen thema-instellingen gebruiken zonder CSS-sjabloon",
|
||||||
"templateSlot": "Slot {{slot}}",
|
"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": {
|
"admin": {
|
||||||
"title": "Beheerpaneel",
|
"title": "Beheerpaneel",
|
||||||
@@ -1309,7 +1346,10 @@
|
|||||||
"archivedEvents": "Gearchiveerde evenementen",
|
"archivedEvents": "Gearchiveerde evenementen",
|
||||||
"systemHealth": "Systeemgezondheid",
|
"systemHealth": "Systeemgezondheid",
|
||||||
"health": {
|
"health": {
|
||||||
"checking": "Controleren..."
|
"checking": "Controleren...",
|
||||||
|
"healthy": "Gezond",
|
||||||
|
"warning": "Waarschuwing",
|
||||||
|
"error": "Fout"
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"available": "Update beschikbaar",
|
"available": "Update beschikbaar",
|
||||||
@@ -1465,8 +1505,70 @@
|
|||||||
"loadingDetail": "Selecties laden…",
|
"loadingDetail": "Selecties laden…",
|
||||||
"detail": {
|
"detail": {
|
||||||
"noComments": "Geen opmerkingen",
|
"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": {
|
"acceptInvitation": {
|
||||||
@@ -1799,7 +1901,13 @@
|
|||||||
},
|
},
|
||||||
"storageDestination": "Opslagbestemming",
|
"storageDestination": "Opslagbestemming",
|
||||||
"backupType": "{{type}} back-up",
|
"backupType": "{{type}} back-up",
|
||||||
"noDestinationSet": "Geen bestemming ingesteld"
|
"noDestinationSet": "Geen bestemming ingesteld",
|
||||||
|
"healthStatus": {
|
||||||
|
"warning": "Waarschuwing",
|
||||||
|
"excellent": "Uitstekend",
|
||||||
|
"good": "Goed",
|
||||||
|
"critical": "Kritiek"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"enableBackup": "Geautomatiseerde back-ups inschakelen",
|
"enableBackup": "Geautomatiseerde back-ups inschakelen",
|
||||||
@@ -2254,7 +2362,14 @@
|
|||||||
"hasComments": "Heeft opmerkingen",
|
"hasComments": "Heeft opmerkingen",
|
||||||
"showingPhotos": "Totaal foto's",
|
"showingPhotos": "Totaal foto's",
|
||||||
"withRatings": "Met beoordelingen",
|
"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": {
|
"export": {
|
||||||
"button": "Exporteren",
|
"button": "Exporteren",
|
||||||
|
|||||||
@@ -161,7 +161,14 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboard": "Painel",
|
"dashboard": "Painel",
|
||||||
"events": "Eventos",
|
"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": {
|
"archives": {
|
||||||
"title": "Arquivos",
|
"title": "Arquivos",
|
||||||
@@ -1312,7 +1319,37 @@
|
|||||||
"noTemplate": "Sem modelo",
|
"noTemplate": "Sem modelo",
|
||||||
"noTemplateDescription": "Usar apenas as definições de tema sem modelo CSS",
|
"noTemplateDescription": "Usar apenas as definições de tema sem modelo CSS",
|
||||||
"templateSlot": "Posição {{slot}}",
|
"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": {
|
"admin": {
|
||||||
"title": "Painel Admin",
|
"title": "Painel Admin",
|
||||||
@@ -1326,7 +1363,10 @@
|
|||||||
"archivedEvents": "Eventos Arquivados",
|
"archivedEvents": "Eventos Arquivados",
|
||||||
"systemHealth": "Saúde do Sistema",
|
"systemHealth": "Saúde do Sistema",
|
||||||
"health": {
|
"health": {
|
||||||
"checking": "Verificando..."
|
"checking": "Verificando...",
|
||||||
|
"healthy": "Saudável",
|
||||||
|
"warning": "Aviso",
|
||||||
|
"error": "Erro"
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"available": "Atualização Disponível",
|
"available": "Atualização Disponível",
|
||||||
@@ -1490,8 +1530,70 @@
|
|||||||
"loadingDetail": "A carregar seleções…",
|
"loadingDetail": "A carregar seleções…",
|
||||||
"detail": {
|
"detail": {
|
||||||
"noComments": "Sem comentários",
|
"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": {
|
"acceptInvitation": {
|
||||||
@@ -1824,7 +1926,13 @@
|
|||||||
},
|
},
|
||||||
"storageDestination": "Destino do Armazenamento",
|
"storageDestination": "Destino do Armazenamento",
|
||||||
"backupType": "Backup {{type}}",
|
"backupType": "Backup {{type}}",
|
||||||
"noDestinationSet": "Nenhum destino definido"
|
"noDestinationSet": "Nenhum destino definido",
|
||||||
|
"healthStatus": {
|
||||||
|
"warning": "Aviso",
|
||||||
|
"excellent": "Excelente",
|
||||||
|
"good": "Bom",
|
||||||
|
"critical": "Crítico"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"enableBackup": "Ativar Backups Automáticos",
|
"enableBackup": "Ativar Backups Automáticos",
|
||||||
@@ -2284,7 +2392,14 @@
|
|||||||
"hasComments": "Possui comentários",
|
"hasComments": "Possui comentários",
|
||||||
"showingPhotos": "Total de fotos",
|
"showingPhotos": "Total de fotos",
|
||||||
"withRatings": "Com avaliações",
|
"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": {
|
"adminLogin": {
|
||||||
"title": "Login Administrativo",
|
"title": "Login Administrativo",
|
||||||
|
|||||||
@@ -164,7 +164,14 @@
|
|||||||
"navigation": {
|
"navigation": {
|
||||||
"dashboard": "Панель управления",
|
"dashboard": "Панель управления",
|
||||||
"events": "События",
|
"events": "События",
|
||||||
"settings": "Настройки"
|
"settings": "Настройки",
|
||||||
|
"archives": "Архивы",
|
||||||
|
"emailSettings": "Настройки e-mail",
|
||||||
|
"branding": "Брендинг",
|
||||||
|
"eventTypes": "Типы событий",
|
||||||
|
"backup": "Резервное копирование",
|
||||||
|
"cmsPages": "CMS-страницы",
|
||||||
|
"users": "Пользователи"
|
||||||
},
|
},
|
||||||
"archives": {
|
"archives": {
|
||||||
"title": "Архивы",
|
"title": "Архивы",
|
||||||
@@ -1329,7 +1336,37 @@
|
|||||||
"noTemplate": "Без шаблона",
|
"noTemplate": "Без шаблона",
|
||||||
"noTemplateDescription": "Использовать только настройки темы без CSS-шаблона",
|
"noTemplateDescription": "Использовать только настройки темы без CSS-шаблона",
|
||||||
"templateSlot": "Слот {{slot}}",
|
"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": {
|
"admin": {
|
||||||
"title": "Панель администратора",
|
"title": "Панель администратора",
|
||||||
@@ -1343,7 +1380,10 @@
|
|||||||
"archivedEvents": "Архивированных событий",
|
"archivedEvents": "Архивированных событий",
|
||||||
"systemHealth": "Состояние системы",
|
"systemHealth": "Состояние системы",
|
||||||
"health": {
|
"health": {
|
||||||
"checking": "Проверка..."
|
"checking": "Проверка...",
|
||||||
|
"healthy": "Исправно",
|
||||||
|
"warning": "Предупреждение",
|
||||||
|
"error": "Ошибка"
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"available": "Доступно обновление",
|
"available": "Доступно обновление",
|
||||||
@@ -1515,8 +1555,70 @@
|
|||||||
"loadingDetail": "Загрузка выборок…",
|
"loadingDetail": "Загрузка выборок…",
|
||||||
"detail": {
|
"detail": {
|
||||||
"noComments": "Нет комментариев",
|
"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": {
|
"acceptInvitation": {
|
||||||
@@ -1849,7 +1951,13 @@
|
|||||||
},
|
},
|
||||||
"storageDestination": "Место хранения",
|
"storageDestination": "Место хранения",
|
||||||
"backupType": "Резервная копия {{type}}",
|
"backupType": "Резервная копия {{type}}",
|
||||||
"noDestinationSet": "Место хранения не задано"
|
"noDestinationSet": "Место хранения не задано",
|
||||||
|
"healthStatus": {
|
||||||
|
"warning": "Предупреждение",
|
||||||
|
"excellent": "Отлично",
|
||||||
|
"good": "Хорошо",
|
||||||
|
"critical": "Критически"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"enableBackup": "Включить автоматическое резервное копирование",
|
"enableBackup": "Включить автоматическое резервное копирование",
|
||||||
@@ -2314,7 +2422,14 @@
|
|||||||
"hasComments": "Есть комментарии",
|
"hasComments": "Есть комментарии",
|
||||||
"showingPhotos": "Всего фото",
|
"showingPhotos": "Всего фото",
|
||||||
"withRatings": "С оценками",
|
"withRatings": "С оценками",
|
||||||
"combineWith": "Объединить с"
|
"combineWith": "Объединить с",
|
||||||
|
"allPhotos": "Все фото",
|
||||||
|
"anyRating": "Любая оценка",
|
||||||
|
"oneStarPlus": "1+ звезда",
|
||||||
|
"twoStarsPlus": "2+ звезды",
|
||||||
|
"threeStarsPlus": "3+ звезды",
|
||||||
|
"fourStarsPlus": "4+ звезды",
|
||||||
|
"fiveStarsOnly": "Только 5 звёзд"
|
||||||
},
|
},
|
||||||
"adminLogin": {
|
"adminLogin": {
|
||||||
"title": "Вход для администратора",
|
"title": "Вход для администратора",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { Button, Card, Loading } from '../../components/common';
|
|||||||
import { UpdateNotification } from '../../components/admin/UpdateNotification';
|
import { UpdateNotification } from '../../components/admin/UpdateNotification';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { eventsService } from '../../services/events.service';
|
import { eventsService } from '../../services/events.service';
|
||||||
import { adminService } from '../../services/admin.service';
|
import { adminService, ActivityType } from '../../services/admin.service';
|
||||||
|
|
||||||
interface StatCard {
|
interface StatCard {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -246,8 +246,8 @@ export const AdminDashboard: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
recentActivity.slice(0, 5).map((activity) => {
|
recentActivity.slice(0, 5).map((activity) => {
|
||||||
// Get color based on activity type
|
// Get color based on activity type
|
||||||
const getActivityColor = (type: string) => {
|
const getActivityColor = (type: ActivityType) => {
|
||||||
const colors: Record<string, string> = {
|
const colors: Partial<Record<ActivityType, string>> = {
|
||||||
'event_created': 'bg-green-500',
|
'event_created': 'bg-green-500',
|
||||||
'photos_uploaded': 'bg-blue-500',
|
'photos_uploaded': 'bg-blue-500',
|
||||||
'event_archived': 'bg-purple-500',
|
'event_archived': 'bg-purple-500',
|
||||||
@@ -264,21 +264,20 @@ export const AdminDashboard: React.FC = () => {
|
|||||||
|
|
||||||
// Format activity message with translations
|
// Format activity message with translations
|
||||||
const getActivityMessage = (): string => {
|
const getActivityMessage = (): string => {
|
||||||
const translationKey = `admin.activities.${activity.type}`;
|
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
eventName: activity.eventName || t('common.unknown'),
|
eventName: activity.eventName || t('common.unknown'),
|
||||||
count: activity.metadata?.count || 0,
|
count: activity.metadata?.count || 0,
|
||||||
template: activity.metadata?.template_key || '',
|
template: activity.metadata?.template_key || '',
|
||||||
categoryName: activity.metadata?.category_name || ''
|
categoryName: activity.metadata?.category_name || ''
|
||||||
};
|
};
|
||||||
|
const translated = t(`admin.activities.${activity.type}`, params);
|
||||||
|
|
||||||
// Translate; if key missing i18n returns the key string itself
|
// Translate; if key missing i18n returns the key string itself
|
||||||
const translated = t(translationKey, params) as string;
|
if (!translated || translated === `admin.activities.${activity.type}`) {
|
||||||
if (!translated || translated === translationKey) {
|
|
||||||
// Fallback: format a readable English message
|
// Fallback: format a readable English message
|
||||||
return adminService.formatActivityMessage(activity);
|
return adminService.formatActivityMessage(activity);
|
||||||
}
|
}
|
||||||
return translated;
|
return translated as string;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import type { ComponentType } from 'react';
|
|
||||||
|
|
||||||
export const BackupManagement: ComponentType<any>;
|
|
||||||
+29
-45
@@ -7,20 +7,9 @@ import {
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
Calendar,
|
|
||||||
Database,
|
|
||||||
FileArchive,
|
|
||||||
Cloud,
|
|
||||||
Server,
|
|
||||||
Loader2,
|
|
||||||
Info,
|
|
||||||
Shield,
|
|
||||||
Clock,
|
Clock,
|
||||||
Download,
|
Loader2,
|
||||||
Upload,
|
Shield,
|
||||||
Trash2,
|
|
||||||
Search,
|
|
||||||
Filter
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
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 { RestoreWizard } from '../../components/admin/RestoreWizard';
|
||||||
import { api } from '../../config/api';
|
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 = () => {
|
export const BackupManagement: React.FC = () => {
|
||||||
const [activeTab, setActiveTab] = useState('dashboard');
|
const [activeTab, setActiveTab] = useState<TabId>('dashboard');
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Tab components with translations
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'dashboard', label: t('backup.tabs.dashboard'), icon: HardDrive },
|
{ id: 'dashboard' as const, label: t('backup.tabs.dashboard'), icon: HardDrive },
|
||||||
{ id: 'configuration', label: t('backup.tabs.configuration'), icon: Settings },
|
{ id: 'configuration' as const, label: t('backup.tabs.configuration'), icon: Settings },
|
||||||
{ id: 'history', label: t('backup.tabs.history'), icon: History },
|
{ id: 'history' as const, label: t('backup.tabs.history'), icon: History },
|
||||||
{ id: 'restore', label: t('backup.tabs.restore'), icon: RefreshCw }
|
{ id: 'restore' as const, label: t('backup.tabs.restore'), icon: RefreshCw },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Fetch backup status
|
|
||||||
const { data: backupStatus, isLoading: statusLoading } = useQuery({
|
const { data: backupStatus, isLoading: statusLoading } = useQuery({
|
||||||
queryKey: ['backup-status'],
|
queryKey: ['backup-status'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await api.get('/admin/backup/status');
|
const response = await api.get('/admin/backup/status');
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
refetchInterval: 10000 // Refresh every 10 seconds
|
refetchInterval: 10000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch backup configuration
|
|
||||||
const { data: backupConfig, isLoading: configLoading } = useQuery({
|
const { data: backupConfig, isLoading: configLoading } = useQuery({
|
||||||
queryKey: ['backup-config'],
|
queryKey: ['backup-config'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await api.get('/admin/backup/config');
|
const response = await api.get('/admin/backup/config');
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger manual backup
|
|
||||||
const manualBackupMutation = useMutation({
|
const manualBackupMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const response = await api.post('/admin/backup/run');
|
const response = await api.post('/admin/backup/run');
|
||||||
@@ -78,15 +63,14 @@ export const BackupManagement = () => {
|
|||||||
toast.success(t('backup.messages.backupStarted'));
|
toast.success(t('backup.messages.backupStarted'));
|
||||||
queryClient.invalidateQueries({ queryKey: ['backup-status'] });
|
queryClient.invalidateQueries({ queryKey: ['backup-status'] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error: any) => {
|
||||||
const message = error.response?.data?.error || t('backup.messages.backupFailed');
|
const message = error.response?.data?.error || t('backup.messages.backupFailed');
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update configuration
|
|
||||||
const updateConfigMutation = useMutation({
|
const updateConfigMutation = useMutation({
|
||||||
mutationFn: async (config) => {
|
mutationFn: async (config: unknown) => {
|
||||||
const response = await api.put('/admin/backup/config', config);
|
const response = await api.put('/admin/backup/config', config);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
@@ -94,10 +78,10 @@ export const BackupManagement = () => {
|
|||||||
toast.success(t('backup.messages.configUpdated'));
|
toast.success(t('backup.messages.configUpdated'));
|
||||||
queryClient.invalidateQueries({ queryKey: ['backup-config'] });
|
queryClient.invalidateQueries({ queryKey: ['backup-config'] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error: any) => {
|
||||||
const message = error.response?.data?.error || t('backup.messages.configUpdateFailed');
|
const message = error.response?.data?.error || t('backup.messages.configUpdateFailed');
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (statusLoading || configLoading) {
|
if (statusLoading || configLoading) {
|
||||||
@@ -142,7 +126,7 @@ export const BackupManagement = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{backupConfig?.backup_enabled && (
|
{backupConfig?.backup_enabled && (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Clock className="h-5 w-5 text-neutral-400" />
|
<Clock className="h-5 w-5 text-neutral-400" />
|
||||||
@@ -156,11 +140,11 @@ export const BackupManagement = () => {
|
|||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => manualBackupMutation.mutate()}
|
onClick={() => manualBackupMutation.mutate()}
|
||||||
disabled={backupStatus?.isRunning || manualBackupMutation.isLoading}
|
disabled={backupStatus?.isRunning || manualBackupMutation.isPending}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{manualBackupMutation.isLoading ? (
|
{manualBackupMutation.isPending ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
{t('backup.actions.starting')}
|
{t('backup.actions.starting')}
|
||||||
@@ -172,7 +156,7 @@ export const BackupManagement = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className={`flex items-center space-x-1 px-3 py-1 rounded-full text-sm font-medium ${
|
<div className={`flex items-center space-x-1 px-3 py-1 rounded-full text-sm font-medium ${
|
||||||
backupConfig?.backup_enabled
|
backupConfig?.backup_enabled
|
||||||
? 'bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-300'
|
? 'bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-300'
|
||||||
@@ -213,30 +197,30 @@ export const BackupManagement = () => {
|
|||||||
{/* Tab Content */}
|
{/* Tab Content */}
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
{activeTab === 'dashboard' && (
|
{activeTab === 'dashboard' && (
|
||||||
<BackupDashboard
|
<BackupDashboard
|
||||||
status={backupStatus}
|
status={backupStatus}
|
||||||
config={backupConfig}
|
config={backupConfig}
|
||||||
onRunBackup={() => manualBackupMutation.mutate()}
|
onRunBackup={() => manualBackupMutation.mutate()}
|
||||||
isBackupRunning={backupStatus?.isRunning || manualBackupMutation.isLoading}
|
isBackupRunning={backupStatus?.isRunning || manualBackupMutation.isPending}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'configuration' && (
|
{activeTab === 'configuration' && (
|
||||||
<BackupConfiguration
|
<BackupConfiguration
|
||||||
config={backupConfig}
|
config={backupConfig}
|
||||||
onSave={(newConfig) => updateConfigMutation.mutate(newConfig)}
|
onSave={(newConfig: unknown) => updateConfigMutation.mutate(newConfig)}
|
||||||
isSaving={updateConfigMutation.isLoading}
|
isSaving={updateConfigMutation.isPending}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'history' && (
|
{activeTab === 'history' && (
|
||||||
<BackupHistory />
|
<BackupHistory />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'restore' && (
|
{activeTab === 'restore' && (
|
||||||
<RestoreWizard />
|
<RestoreWizard />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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 {
|
export interface Activity {
|
||||||
id: number;
|
id: number;
|
||||||
type: string;
|
type: ActivityType;
|
||||||
actorType: string;
|
actorType: string;
|
||||||
actorName: string;
|
actorName: string;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user